This commit is contained in:
Josef 2025-03-08 13:22:18 +01:00
parent 3671fc067f
commit a2152bfec6
24 changed files with 897 additions and 13 deletions

223
browser/i18n/Countries.ts Normal file
View File

@ -0,0 +1,223 @@
import { Lexer } from "../text/lexer/Lexer";
import { LexerMatcher } from "../text/lexer/LexerMatcher";
import { LexerMatcherLibrary } from "../text/lexer/LexerMatcherLibrary";
import { RegExpUtility } from "../text/RegExpUtitlity";
import { MultiString } from "./MultiString";
export class Country
{
name:MultiString;
flag:string;
}
export class Countries
{
static _countryMap:Map<string,Country>;
static _lexer:Lexer;
static _createLexerAndMap()
{
if ( Countries._lexer != null )
{
return;
}
let lexer = new Lexer();
let countryMap = new Map<string,Country>();
Countries.list.forEach(
( c )=>
{
lexer.addMatcher( new LexerMatcher( c.name.en, RegExpUtility.createMatcher( c.name.en ) ) );
countryMap.set( c.name.en, c );
}
);
lexer.addMatcher( LexerMatcherLibrary.BREAK_MATCHER );
lexer.addMatcher( LexerMatcherLibrary.ANY_SYMBOL_MATCHER );
this._lexer = lexer;
this._countryMap = countryMap;
}
static get lexer()
{
this._createLexerAndMap();
return this._lexer;
}
static get countryNameMap()
{
this._createLexerAndMap();
return this._countryMap;
}
static list:Country[] =
[
{
name:{ en: "Sweden", de: "Schweden" },
flag: "🇸🇪"
},
{
name:{ en: "South Africa", de: "Südafrika" },
flag: "🇿🇦"
},
{
name:{ en: "Netherlands", de: "Niederlande" },
flag: "🇳🇱"
},
{
name:{ en: "Portugal", de: "Portugal" },
flag: "🇵🇹"
},
{
name:{ en: "France", de: "Frankreich" },
flag: "🇫🇷"
},
{
name:{ en: "Jamaica", de: "Jamaika" },
flag: "🇯🇲"
},
{
name:{ en: "Italy", de: "Italien" },
flag: "🇮🇹"
},
{
name:{ en: "Argentina", de: "Argentinien" },
flag: "🇦🇷"
},
{
name:{ en: "Germany", de: "Deutschland" },
flag: "🇩🇪"
},
{
name:{ en: "Morocco", de: "Marokko" },
flag: "🇲🇦"
},
{
name:{ en: "Brasil", de: "Brasilien" },
flag: "🇧🇷"
},
{
name:{ en: "Panama", de: "Panama" },
flag: "🇵🇦"
},
{
name:{ en: "Colombia", de: "Kolumbien" },
flag: "🇨🇴"
},
{
name:{ en: "South Korea", de: "Südkorea" },
flag: "🇰🇷"
},
{
name:{ en: "New Zealand", de: "Neuseeland" },
flag: "🇳🇿"
},
{
name:{ en: "Swiss", de: "Schweiz" },
flag: "🇨🇭"
},
{
name:{ en: "Norway", de: "Norwegen" },
flag: "🇳🇴"
},
{
name:{ en: "Japan", de: "Japan" },
flag: "🇯🇵"
},
{
name:{ en: "Costa Rica", de: "Costa Rica" },
flag: "🇨🇷"
},
{
name:{ en: "Spain", de: "Spanien" },
flag: "🇪🇸"
},
{
name:{ en: "Zambia", de: "Sambia" },
flag: "🇿🇲"
},
{
name:{ en: "Canada", de: "Kanada" },
flag: "🇨🇦"
},
{
name:{ en: "Ireland", de: "Irland" },
flag: "🇮🇪"
},
{
name:{ en: "USA", de: "USA" },
flag: "🇺🇸"
},
{
name:{ en: "Vietnam", de: "Vietnam" },
flag: "🇻🇳"
},
{
name:{ en: "Australia", de: "Australien" },
flag: "🇦🇺"
},
{
name:{ en: "Nigeria", de: "Nigerien" },
flag: "🇳🇬"
},
{
name:{ en: "England", de: "England" },
flag: "🏴󠁧󠁢󠁥󠁮󠁧󠁿"
},
{
name:{ en: "Denmark", de: "Dänemark" },
flag: "🇩🇰"
},
{
name:{ en: "China", de: "China" },
flag: "🇨🇳"
},
{
name:{ en: "Haiti", de: "Haiti" },
flag: "🇭🇹"
},
{
name:{ en: "Philippines", de: "Philippinen" },
flag: "🇵🇭"
}
]
}

View File

@ -0,0 +1,89 @@
import { ElementType } from "../dom/ElementType";
export type de = "de";
export type en = "en";
export type es = "es";
export type fr = "fr";
export type it = "it";
export type ja = "ja";
export type pt = "pt";
export type ru = "ru";
export type zh = "zh";
export type LanguageCode =
de |
en |
es |
fr |
it |
ja |
pt |
ru |
zh
;
export class LanguageCodes
{
static #current:LanguageCode = "en";
static get current(){ return this.#current; }
static setCurrent( lc:LanguageCode )
{
this.#current = lc;
}
static fallback:LanguageCode = "en";
static readonly de:de = "de";
static readonly en:en = "en";
static readonly es:es = "es";
static readonly fr:fr = "fr";
static readonly it:it = "it";
static readonly ja:ja = "ja";
static readonly pt:pt = "pt";
static readonly ru:ru = "ru";
static readonly zh:zh = "zh";
static #htmlTypes = new Map<LanguageCode,ElementType<Element>>();
static htmlType( code:LanguageCode ):ElementType<Element>
{
if ( this.#htmlTypes.has( code ) )
{
return this.#htmlTypes.get( code );
}
this.#htmlTypes.set( code, new ElementType( "lang--" + code ) );
return this.#htmlTypes.get( code );
}
static get all()
{
let list =
[
LanguageCodes.de,
LanguageCodes.en,
LanguageCodes.es,
LanguageCodes.fr,
LanguageCodes.it,
LanguageCodes.ja,
LanguageCodes.pt,
LanguageCodes.ru,
LanguageCodes.zh
];
return list;
}
static stringToLanguageCode( value:string )
{
value = value.toLowerCase();
let index = LanguageCodes.all.indexOf( value as LanguageCode );
return index === -1 ? null : LanguageCodes.all[ index ];
}
}

100
browser/i18n/MultiString.ts Normal file
View File

@ -0,0 +1,100 @@
import { ElementType } from "../dom/ElementType";
import { LanguageCode, LanguageCodes } from "./LanguageCode";
export class MultiString
{
en:string;
de?:string;
fr?:string;
es?:string;
pt?:string;
it?:string;
ru?:string;
ja?:string;
zh?:string;
static _htmlType:ElementType<any>;
static create( value:string )
{
let ms = new MultiString();
ms.en = value;
return ms;
}
static resolve( value:string|MultiString )
{
if ( typeof value === "string" )
{
return value;
}
let ms = value as MultiString;
return MultiString.get( ms, LanguageCodes.current, LanguageCodes.fallback );
}
static get htmlType()
{
if ( MultiString._htmlType )
{
return MultiString._htmlType;
}
MultiString._htmlType = new ElementType( "multi--string" );
return MultiString._htmlType;
}
static asArray( ms:MultiString)
{
return [ ms.en, ms.de, ms.fr, ms.es, ms.pt, ms.it, ms.ru, ms.ja, ms.zh ];
}
static get( ms:MultiString, code:LanguageCode, fallback:LanguageCode = LanguageCodes.en )
{
//console.log( "get > ms", ms );
let value = MultiString.fromCode( ms, code );
if ( value )
{
return value;
}
return MultiString.fromCode( ms, fallback );
}
static set( ms:MultiString, code:LanguageCode, value:string )
{
switch ( code )
{
case LanguageCodes.en: { ms.en = value; } break;
case LanguageCodes.de: { ms.de = value; } break;
case LanguageCodes.fr: { ms.fr = value; } break;
case LanguageCodes.es: { ms.es = value; } break;
case LanguageCodes.pt: { ms.pt = value; } break;
case LanguageCodes.it: { ms.it = value; } break;
case LanguageCodes.ru: { ms.ru = value; } break;
case LanguageCodes.ja: { ms.ja = value; } break;
case LanguageCodes.zh: { ms.zh = value; } break;
}
}
static fromCode( ms:MultiString, code:LanguageCode )
{
switch ( code )
{
case LanguageCodes.en: return ms.en;
case LanguageCodes.de: return ms.de;
case LanguageCodes.fr: return ms.fr;
case LanguageCodes.es: return ms.es;
case LanguageCodes.pt: return ms.pt;
case LanguageCodes.it: return ms.it;
case LanguageCodes.ru: return ms.ru;
case LanguageCodes.ja: return ms.ja;
case LanguageCodes.zh: return ms.zh;
}
}
}

View File

@ -0,0 +1,18 @@
export type DE = "DE";
export type UK = "UK";
export type US = "US";
export type RegionCode =
DE |
UK |
US
;
export class RegionCodes
{
static readonly DE:DE = "DE";
static readonly UK:UK = "UK";
static readonly US:US = "US";
}

View File

@ -0,0 +1,4 @@
export class SerializedMultiString
{
}

View File

@ -0,0 +1,15 @@
import { EN_Common } from "../en/EN_Common";
export class DE_Common extends EN_Common
{
userAccount =
{
login:"Einloggen",
create:"Erstellen",
email:"Email",
password:"Passwort",
name:"Name",
forgotPassword:"Passwort vergessen?",
terms:"Nutzungsbedingungen"
}
}

View File

@ -0,0 +1,30 @@
import { leadingZeros } from '../../text/leadingZeros';
import { EN_Locale } from '../en/EN_Locale';
export class DE_Locale extends EN_Locale
{
Date =
{
Calender:( date:Date ) =>
{
let month = date.getMonth();
let day = date.getDate();
let year = date.getFullYear();
return `${day}.${month}.${year}`
},
Time:( hours24:number, minutes:number ) =>
{
return `${leadingZeros( hours24 )}:${leadingZeros( minutes)}`
},
}
Dialog =
{
Dialog: "Dialog",
Confirm: "OK",
Cancel: "Abbrechen",
Path:"Pfad",
Name:"Name"
}
}

View File

@ -0,0 +1,13 @@
export class EN_Common
{
userAccount =
{
login:"Login",
create:"Create",
email:"Email",
password:"Password",
name:"Name",
forgotPassword:"Forgot password?",
terms:"Terms & conditions"
}
}

View File

@ -0,0 +1,38 @@
import { leadingZeros } from "../../text/leadingZeros";
export class EN_Locale
{
Date =
{
Calender:( date:Date ) =>
{
let month = date.getMonth();
let day = date.getDate();
let year = date.getFullYear();
return `${month}/${day}/${year}`
},
Time:( hours24:number, minutes:number ) =>
{
let hours12 = hours24 % 12;
let period = hours24 < 12 ? "a" : "p";
if ( hours12 === 0 )
{
hours12 = 12;
}
return `${leadingZeros( hours12 )}:${leadingZeros( minutes)} ${period}m`
},
}
Dialog =
{
Dialog: "Dialog",
Confirm: "OK",
Cancel: "Cancel",
Path:"Path",
Name:"Name"
}
}

View File

@ -0,0 +1,65 @@
import { MessageType, MessageTypes } from './MessageType';
export class Message
{
type:MessageType;
content:string;
static with( type:MessageType, content:string )
{
let message = new Message();
message.type =type;
message.content = content;
return message;
}
static Info( ...content:any[] )
{
return Message.with( MessageTypes.Info, content.join( ", " ) );
}
static addInfo( messages:Message[], ...content:any[] )
{
messages.push( Message.with( MessageTypes.Info, content.join( " " ) ) );
}
static Error( ...content:any[] )
{
return Message.with( MessageTypes.Error, content.join( " " ) );
}
static addError( messages:Message[], ...content:any[] )
{
messages.push( Message.with( MessageTypes.Error, content.join( " " ) ) );
}
static Warning( ...content:any[] )
{
return Message.with( MessageTypes.Warning, content.join( " " ) );
}
static addWarning( messages:Message[], ...content:any[] )
{
messages.push( Message.with( MessageTypes.Warning, content.join( " " ) ) );
}
static toConsole( message:Message )
{
if ( MessageTypes.Error == message.type )
{
console.error( message.content );
return
}
if ( MessageTypes.Warning == message.type )
{
console.warn( message.content );
return
}
console.log( message.content );
}
}

View File

@ -0,0 +1,22 @@
export class MessageColors
{
static gray( text:string )
{
return text;
}
static yellow( text:string )
{
return text;
}
static black( text:string )
{
return text;
}
static bgRed( text:string )
{
return text;
}
}

View File

@ -0,0 +1,66 @@
import { Message } from './Message';
import { MessageTypes } from './MessageType';
export class MessageReader
{
private _messages:Message[] = [];
private _scanned = false;
private _errors = new Set<number>();
private _warnings = new Set<number>();
constructor( messages:Message[] = null )
{
this._messages = messages || [];
}
set messages( messages:Message[] )
{
this._messages = messages;
this._scanned = false;
}
get messages()
{
return this._messages;
}
private _scan()
{
if ( this._scanned )
{
return;
}
this._scanned = true;
this._errors.clear();
this._warnings.clear();
for ( let i = 0; i < this._messages.length; i++ )
{
if ( MessageTypes.Error === this._messages[ i ].type )
{
this._errors.add( i );
}
if ( MessageTypes.Warning === this._messages[ i ].type )
{
this._warnings.add( i );
}
}
}
get hasErrors()
{
this._scan();
return this._errors.size > 0;
}
get hasWarnings()
{
this._scan();
return this._warnings.size > 0;
}
}

View File

@ -0,0 +1,28 @@
import { ClassFlag } from "../dom/ClassFlag";
import { ElementType } from "../dom/ElementType";
import { Message } from "./Message";
import { MessageTypes } from "./MessageType";
export class MessageToHTML
{
static readonly typeToColor = new ClassFlag( "message--type--to--color" );
static readonly typeToBackground = new ClassFlag( "message--type--to--background" );
static types =
{
[ MessageTypes.Verbose ]: new ElementType<HTMLElement>( "message--verbose" ),
[ MessageTypes.Info ]: new ElementType<HTMLElement>( "message--info" ),
[ MessageTypes.Warning ]: new ElementType<HTMLElement>( "message--warning" ),
[ MessageTypes.Error ]: new ElementType<HTMLElement>( "message--error" ),
}
static convert( message:Message ):HTMLElement
{
return MessageToHTML.types[ message.type ].create( message.content );
}
static addTo( target:Element, messages:Message[] )
{
messages.forEach( m => target.appendChild( MessageToHTML.convert( m ) ) );
}
}

View File

@ -0,0 +1,30 @@
import { Message } from "./Message";
export type Verbose = "Verbose";
export type Info = "Info";
export type Warning = "Warning";
export type Error = "Error";
export type MessageType = Verbose | Info | Warning | Error;
export class MessageTypes
{
static readonly Verbose:Verbose = "Verbose";
static readonly Info:Info = "Info";
static readonly Warning:Warning = "Warning";
static readonly Error:Error = "Error";
static getLevel( type:MessageType ):number
{
switch ( type )
{
case MessageTypes.Verbose: return 0;
case MessageTypes.Info: return 1;
case MessageTypes.Warning: return 2;
case MessageTypes.Error: return 3;
}
return -1;
}
}

View File

@ -0,0 +1,114 @@
import { Message } from './Message';
import { MessageType, MessageTypes, Verbose } from './MessageType';
import { MessageColors } from "./MessageColors";
export class MessageWriter
{
protected _messages:Message[] = [];
autologToConsole:boolean = false;
consoleLogLevel:number = 1;
constructor( messages:Message[] = null )
{
this._messages = messages || [];
}
set messages( messages:Message[] )
{
this._messages = messages;
}
get messages()
{
return this._messages;
}
logMessages()
{
let mw = new MessageWriter();
mw.autologToConsole = true;
this._messages.forEach( m => mw.addMessage( m ) );
}
addMessage( message:Message )
{
let colors = MessageColors;
/*let message = new Message();
message.type = type;
message.content = content;
*/
let type = message.type;
let content = message.content;
this._messages.push( message );
if ( this.autologToConsole )
{
if ( this.consoleLogLevel > MessageTypes.getLevel( type ) )
{
return;
}
switch ( type )
{
case MessageTypes.Verbose:
{
console.log( colors.gray( content ) );
}
break;
case MessageTypes.Info:
{
console.log( content );
};
break;
case MessageTypes.Warning:
{
console.warn( colors.yellow( content ) );
};
break;
case MessageTypes.Error:
{
console.error( colors.bgRed( colors.black( content ) ) );
};
break;
}
}
}
add( type:MessageType, content:string )
{
let message = new Message();
message.type = type;
message.content = content;
this.addMessage( message );
}
verbose( content:string )
{
this.add( MessageTypes.Verbose, content );
}
info( content:string )
{
this.add( MessageTypes.Info, content );
}
warn( content:string )
{
this.add( MessageTypes.Warning, content );
}
error( content:string )
{
this.add( MessageTypes.Error, content );
}
}

View File

@ -0,0 +1,6 @@
import { Message } from "./Message";
export class TimestampMessage extends Message
{
timestamp:string;
}

View File

@ -0,0 +1,18 @@
export function leadingZeros( value:number, numDigits:number = 2 )
{
if ( numDigits <= 1 )
{
return value;
}
let hasMinus = value < 0;
let stringValue = Math.abs( value ) + "";
while ( stringValue.length < numDigits )
{
stringValue = "0" + stringValue;
}
return ( hasMinus ? "-" : "" ) + stringValue;
}

View File

@ -1,5 +1,6 @@
import { MultiString } from "../../../i18n/MultiString"; import { MultiString } from "../../../i18n/MultiString";
import { MessageType, MessageTypes } from "../../../messages/MessageType"; import { MessageType, MessageTypes } from "../../../messages/MessageType";
import { ParserMessage } from "./ParserMessage"; import { ParserMessage } from "./ParserMessage";
import { ParserPhase } from "./ParserPhase"; import { ParserPhase } from "./ParserPhase";
import { SourceInfo } from "./SourceInfo"; import { SourceInfo } from "./SourceInfo";

View File

@ -1,3 +1,4 @@
import { Message } from "../../../messages/Message"; import { Message } from "../../../messages/Message";
import { MessageType } from "../../../messages/MessageType"; import { MessageType } from "../../../messages/MessageType";
import { SourceInfo } from "./SourceInfo"; import { SourceInfo } from "./SourceInfo";

View File

@ -1,4 +1,4 @@
import { RJExpressionNode } from "../../../rj/expressions/RJExpressionNode";
import { LexerEvent } from "../LexerEvent"; import { LexerEvent } from "../LexerEvent";
export class SourceRange export class SourceRange
@ -38,7 +38,8 @@ export class SourceRange
return range; return range;
} }
/*
static combineNodes( a:RJExpressionNode, b:RJExpressionNode ) static combineNodes( a:RJExpressionNode, b:RJExpressionNode )
{ {
return SourceRange.combine( a.sourceRange, b.sourceRange ); return SourceRange.combine( a.sourceRange, b.sourceRange );
@ -53,7 +54,7 @@ export class SourceRange
{ {
return SourceRange.fromNodes( nodes, 0, nodes.length - 1 ); return SourceRange.fromNodes( nodes, 0, nodes.length - 1 );
} }
*/
} }

View File

@ -1,4 +1,4 @@
import { Files } from "../../node/Files";
import { TextReplacer } from "./TextReplacer"; import { TextReplacer } from "./TextReplacer";
export class TextReplacementProcessor export class TextReplacementProcessor
@ -14,11 +14,11 @@ export class TextReplacementProcessor
return text; return text;
} }
replaceFile( filePath:string, suffix:string = ".replaced.html") /*replaceFile( filePath:string, suffix:string = ".replaced.html")
{ {
let newFilePath = filePath + suffix; let newFilePath = filePath + suffix;
let text = Files.loadUTF8( filePath ); let text = Files.loadUTF8( filePath );
let replacedText = this.process( text ); let replacedText = this.process( text );
Files.saveUTF8( newFilePath, replacedText ); Files.saveUTF8( newFilePath, replacedText );
} }*/
} }

View File

@ -1,5 +1,5 @@
import { ExtendedRegex } from "../ExtendedRegex"; import { ExtendedRegex } from "../ExtendedRegex";
import { RegExpUtility } from "../RegExpUtility"; import { RegExpUtility } from "../RegExpUtitlity";
import { RegexReplacement, TextReplacement } from "./TextReplacement"; import { RegexReplacement, TextReplacement } from "./TextReplacement";
import { TextSelectionRange } from "./TextSelectionRange"; import { TextSelectionRange } from "./TextSelectionRange";
import { RegexStartEndSelector, TextSelector } from "./TextSelector"; import { RegexStartEndSelector, TextSelector } from "./TextSelector";
@ -103,7 +103,7 @@ export class TextReplacer
let regexSource = TextReplacer.removeAttributeREgex.source.replace( /ATTRIBUTE/g, attribute ); let regexSource = TextReplacer.removeAttributeREgex.source.replace( /ATTRIBUTE/g, attribute );
let matcher = ExtendedRegex.create( regexSource ); let matcher = ExtendedRegex.create( regexSource );
let replacement = `$1 $2`; let replacement = `$1 $2`;
this.addRegexReplacement( matcher, replacement ); this.addRegexReplacement( matcher, replacement );

View File

@ -1,6 +1,6 @@
import { RegExpUtility } from "../RegExpUtility";
import { TextSelectionRange } from "./TextSelectionRange"; import { TextSelectionRange } from "./TextSelectionRange";
import { ColorConsole } from "../../node/ColorConsole"; import { RegExpUtility } from "../RegExpUtitlity";
export abstract class TextSelector export abstract class TextSelector
{ {
@ -17,10 +17,12 @@ export class RegexStartEndSelector extends TextSelector
toString() toString()
{ {
let start = ColorConsole.fg( this.startRegex.source, ColorConsole.red ); /*let start = ColorConsole.fg( this.startRegex.source, ColorConsole.red );
let end = ColorConsole.fg( this.endRegex.source, ColorConsole.red ); let end = ColorConsole.fg( this.endRegex.source, ColorConsole.red );
let info = `RegexSelector{${start} ${end} ${this.multiple?"multiple":""} ${this.inner?"inner":""}}`; let info = `RegexSelector{${start} ${end} ${this.multiple?"multiple":""} ${this.inner?"inner":""}}`;
return info; return info;*/
return this.inner;
} }
select( source:string ) select( source:string )

View File

@ -1,4 +1,4 @@
import { RegExpUtility } from "../RegExpUtility"; import { RegExpUtility } from "../RegExpUtitlity";
export type Variables = {[index:string]:string } export type Variables = {[index:string]:string }
export class VariableReplacer export class VariableReplacer