fix: wrap up changes for strings localization
parent
da39198f97
commit
62a3d4e9be
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "script"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +1,19 @@
|
|||||||
/** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.getMessage } and {@link window.i18n } */
|
/** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.getMessage } and {@link window.i18n } */
|
||||||
|
|
||||||
import type { SetupI18nReturnType } from '../../../types/localizer';
|
import type { SetupI18nReturnType } from '../../../types/localizer';
|
||||||
import { i18nLog } from '../shared';
|
|
||||||
import { localizeFromOld } from '../localizedString';
|
|
||||||
import {
|
import {
|
||||||
ArgsFromToken,
|
|
||||||
formatMessageWithArgs,
|
formatMessageWithArgs,
|
||||||
GetMessageArgs,
|
|
||||||
getRawMessage,
|
getRawMessage,
|
||||||
inEnglish,
|
inEnglish,
|
||||||
MergedLocalizerTokens,
|
|
||||||
stripped,
|
stripped,
|
||||||
|
getMessageDefault,
|
||||||
} from '../../../localization/localeTools';
|
} from '../../../localization/localeTools';
|
||||||
|
|
||||||
/**
|
const getMessageDefaultCopy: any = getMessageDefault;
|
||||||
* Retrieves a localized message string, substituting variables where necessary.
|
|
||||||
*
|
|
||||||
* @param token - The token identifying the message to retrieve.
|
|
||||||
* @param args - An optional record of substitution variables and their replacement values. This is required if the string has dynamic variables.
|
|
||||||
*
|
|
||||||
* @returns The localized message string with substitutions applied.
|
|
||||||
*/
|
|
||||||
function getMessageDefault<T extends MergedLocalizerTokens>(...props: GetMessageArgs<T>): string {
|
|
||||||
const token = props[0];
|
|
||||||
|
|
||||||
try {
|
getMessageDefaultCopy.inEnglish = inEnglish;
|
||||||
return localizeFromOld(props[0], props[1] as ArgsFromToken<T>).toString();
|
getMessageDefaultCopy.stripped = stripped;
|
||||||
} catch (error) {
|
getMessageDefaultCopy.getRawMessage = getRawMessage;
|
||||||
i18nLog(error.message);
|
getMessageDefaultCopy.formatMessageWithArgs = formatMessageWithArgs;
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMessageDefault.inEnglish = inEnglish;
|
|
||||||
getMessageDefault.stripped = stripped;
|
|
||||||
getMessageDefault.getRawMessage = getRawMessage;
|
|
||||||
getMessageDefault.formatMessageWithArgs = formatMessageWithArgs;
|
|
||||||
|
|
||||||
export const getMessage: SetupI18nReturnType = getMessageDefault as SetupI18nReturnType;
|
export const getMessage: SetupI18nReturnType = getMessageDefault as SetupI18nReturnType;
|
||||||
|
@ -1,189 +0,0 @@
|
|||||||
import { pluralsDictionary, simpleDictionary } from '../../localization/locales';
|
|
||||||
import {
|
|
||||||
ArgsFromToken,
|
|
||||||
deSanitizeHtmlTags,
|
|
||||||
getStringForRule,
|
|
||||||
isPluralToken,
|
|
||||||
isSimpleToken,
|
|
||||||
MergedLocalizerTokens,
|
|
||||||
sanitizeArgs,
|
|
||||||
} from '../../localization/localeTools';
|
|
||||||
import { i18nLog, getCrowdinLocale } from './shared';
|
|
||||||
import { CrowdinLocale, LOCALE_DEFAULTS } from '../../localization/constants';
|
|
||||||
|
|
||||||
type ArgString = `${string}{${string}}${string}`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a string contains a dynamic variable.
|
|
||||||
* @param localizedString - The string to check.
|
|
||||||
* @returns `true` if the string contains a dynamic variable, otherwise `false`.
|
|
||||||
*/
|
|
||||||
const isStringWithArgs = (localizedString: string): localizedString is ArgString =>
|
|
||||||
localizedString.includes('{');
|
|
||||||
|
|
||||||
const isReplaceLocalizedStringsWithKeysEnabled = () =>
|
|
||||||
!!(typeof window !== 'undefined' && window?.sessionFeatureFlags?.replaceLocalizedStringsWithKeys);
|
|
||||||
|
|
||||||
export class LocalizedStringBuilder<T extends MergedLocalizerTokens> extends String {
|
|
||||||
private readonly token: T;
|
|
||||||
private args?: ArgsFromToken<T>;
|
|
||||||
private isStripped = false;
|
|
||||||
private isEnglishForced = false;
|
|
||||||
|
|
||||||
private readonly renderStringAsToken = isReplaceLocalizedStringsWithKeysEnabled();
|
|
||||||
|
|
||||||
constructor(token: T) {
|
|
||||||
super(token);
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toString(): string {
|
|
||||||
try {
|
|
||||||
if (this.renderStringAsToken) {
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawString = this.getRawString();
|
|
||||||
const str = isStringWithArgs(rawString) ? this.formatStringWithArgs(rawString) : rawString;
|
|
||||||
|
|
||||||
if (this.isStripped) {
|
|
||||||
return this.postProcessStrippedString(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
} catch (error) {
|
|
||||||
i18nLog(error);
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withArgs(args: ArgsFromToken<T>): Omit<this, 'withArgs'> {
|
|
||||||
this.args = args;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
forceEnglish(): Omit<this, 'forceEnglish'> {
|
|
||||||
this.isEnglishForced = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
strip(): Omit<this, 'strip'> {
|
|
||||||
const sanitizedArgs = this.args ? sanitizeArgs(this.args, '\u200B') : undefined;
|
|
||||||
if (sanitizedArgs) {
|
|
||||||
this.args = sanitizedArgs as ArgsFromToken<T>;
|
|
||||||
}
|
|
||||||
this.isStripped = true;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private postProcessStrippedString(str: string): string {
|
|
||||||
const strippedString = str.replaceAll(/<[^>]*>/g, '');
|
|
||||||
return deSanitizeHtmlTags(strippedString, '\u200B');
|
|
||||||
}
|
|
||||||
|
|
||||||
private localeToTarget(): CrowdinLocale {
|
|
||||||
return this.isEnglishForced ? 'en' : getCrowdinLocale();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRawString(): string {
|
|
||||||
try {
|
|
||||||
if (this.renderStringAsToken) {
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSimpleToken(this.token)) {
|
|
||||||
return simpleDictionary[this.token][this.localeToTarget()];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPluralToken(this.token)) {
|
|
||||||
throw new Error('invalid token provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.resolvePluralString();
|
|
||||||
} catch (error) {
|
|
||||||
i18nLog(error.message);
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolvePluralString(): string {
|
|
||||||
const pluralKey = 'count' as const;
|
|
||||||
|
|
||||||
let num: number | string | undefined = this.args?.[pluralKey as keyof ArgsFromToken<T>];
|
|
||||||
|
|
||||||
if (num === undefined) {
|
|
||||||
i18nLog(
|
|
||||||
`Attempted to get plural count for missing argument '${pluralKey} for token '${this.token}'`
|
|
||||||
);
|
|
||||||
num = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof num !== 'number') {
|
|
||||||
i18nLog(
|
|
||||||
`Attempted to get plural count for argument '${pluralKey}' which is not a number for token '${this.token}'`
|
|
||||||
);
|
|
||||||
num = parseInt(num, 10);
|
|
||||||
if (Number.isNaN(num)) {
|
|
||||||
i18nLog(
|
|
||||||
`Attempted to get parsed plural count for argument '${pluralKey}' which is not a number for token '${this.token}'`
|
|
||||||
);
|
|
||||||
num = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const localeToTarget = this.localeToTarget();
|
|
||||||
const cardinalRule = new Intl.PluralRules(localeToTarget).select(num);
|
|
||||||
|
|
||||||
if (!isPluralToken(this.token)) {
|
|
||||||
throw new Error('resolvePluralString can only be called with a plural string');
|
|
||||||
}
|
|
||||||
|
|
||||||
let pluralString = getStringForRule({
|
|
||||||
cardinalRule,
|
|
||||||
crowdinLocale: localeToTarget,
|
|
||||||
dictionary: pluralsDictionary,
|
|
||||||
token: this.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pluralString) {
|
|
||||||
i18nLog(
|
|
||||||
`Plural string not found for cardinal '${cardinalRule}': '${this.token}' Falling back to 'other' cardinal`
|
|
||||||
);
|
|
||||||
|
|
||||||
pluralString = getStringForRule({
|
|
||||||
cardinalRule: 'other',
|
|
||||||
crowdinLocale: localeToTarget,
|
|
||||||
dictionary: pluralsDictionary,
|
|
||||||
token: this.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pluralString) {
|
|
||||||
i18nLog(`Plural string not found for fallback cardinal 'other': '${this.token}'`);
|
|
||||||
|
|
||||||
return this.token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluralString.replaceAll('#', `${num}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatStringWithArgs(str: ArgString): string {
|
|
||||||
/** Find and replace the dynamic variables in a localized string and substitute the variables with the provided values */
|
|
||||||
return str.replace(/\{(\w+)\}/g, (match, arg: string) => {
|
|
||||||
const matchedArg = this.args
|
|
||||||
? this.args[arg as keyof ArgsFromToken<T>]?.toString()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return matchedArg ?? LOCALE_DEFAULTS[arg as keyof typeof LOCALE_DEFAULTS] ?? match;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function localize<T extends MergedLocalizerTokens>(token: T) {
|
|
||||||
return new LocalizedStringBuilder<T>(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function localizeFromOld<T extends MergedLocalizerTokens>(token: T, args: ArgsFromToken<T>) {
|
|
||||||
return new LocalizedStringBuilder<T>(token).withArgs(args);
|
|
||||||
}
|
|
Loading…
Reference in New Issue