feat: create html stripper for i18n strings and add plurals support

pull/3206/head
Ryan Miller 9 months ago
parent 0c015b4ea7
commit c0d9410094

@ -14,6 +14,7 @@ import {
import timeLocales from 'date-fns/locale'; import timeLocales from 'date-fns/locale';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { import {
DictionaryWithoutPluralStrings,
GetMessageArgs, GetMessageArgs,
LocalizerDictionary, LocalizerDictionary,
LocalizerToken, LocalizerToken,
@ -23,6 +24,7 @@ import {
import { DURATION_SECONDS, LOCALE_DEFAULTS } from '../session/constants'; import { DURATION_SECONDS, LOCALE_DEFAULTS } from '../session/constants';
import { updateLocale } from '../state/ducks/dictionary'; import { updateLocale } from '../state/ducks/dictionary';
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime'; import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { Dictionary } from '../localization/locales';
export function loadDictionary(locale: Locale) { export function loadDictionary(locale: Locale) {
return import(`../../_locales/${locale}/messages.json`) as Promise<LocalizerDictionary>; return import(`../../_locales/${locale}/messages.json`) as Promise<LocalizerDictionary>;
@ -98,32 +100,30 @@ const timeLocaleMap = {
export type Locale = keyof typeof timeLocaleMap; export type Locale = keyof typeof timeLocaleMap;
const enPluralFormRegex = /\{(\w+), plural, one \{(\w+)\} other \{(\w+)\}\}/;
const cardinalPluralRegex: Record<Intl.LDMLPluralRule, RegExp> = { const cardinalPluralRegex: Record<Intl.LDMLPluralRule, RegExp> = {
zero: /(zero) \{([^}]*)\}/, zero: /zero \[(.*?)\]/g,
one: /(one) \{([^}]*)\}/, one: /one \[(.*?)\]/g,
two: /(two) \{([^}]*)\}/, two: /two \[(.*?)\]/g,
few: /(few) \{([^}]*)\}/, few: /few \[(.*?)\]/g,
many: /(many) \{([^}]*)\}/, many: /many \[(.*?)\]/g,
other: /(other) \{([^}]*)\}/, other: /other \[(.*?)\]/g,
}; };
function getPluralKey(string: PluralString): PluralKey | undefined { function getPluralKey<R extends PluralKey | undefined>(string: PluralString): R {
const match = string.match(enPluralFormRegex); const match = /{(\w+), plural, one \[.+\] other \[.+\]}/g.exec(string);
return match && match[1] ? match[1] : undefined; return (match?.[1] ?? undefined) as R;
} }
function getStringForCardinalRule( function getStringForCardinalRule(
localizedString: string, localizedString: string,
cardinalRule: Intl.LDMLPluralRule cardinalRule: Intl.LDMLPluralRule
): string | undefined { ): string | undefined {
const match = localizedString.match(cardinalPluralRegex[cardinalRule]); const match = cardinalPluralRegex[cardinalRule].exec(localizedString);
return match ? match[2] : undefined; return match?.[1] ?? undefined;
} }
const isPluralForm = (localizedString: string): localizedString is PluralString => const isPluralForm = (localizedString: string): localizedString is PluralString =>
enPluralFormRegex.test(localizedString); /{\w+, plural, one \[.+\] other \[.+\]}/g.test(localizedString);
/** /**
* Logs an i18n message to the console. * Logs an i18n message to the console.
@ -237,19 +237,21 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => {
} }
/** Find and replace the dynamic variables in a localized string and substitute the variables with the provided values */ /** Find and replace the dynamic variables in a localized string and substitute the variables with the provided values */
// @ts-expect-error TODO: Fix this type, now that we have plurals it doesnt quite work return (localizedString as DictionaryWithoutPluralStrings[T]).replace(
return localizedString.replace(/\{(\w+)\}/g, (match, arg: keyof typeof args) => { /\{(\w+)\}/g,
const substitution: string | undefined = args?.[arg]; (match, arg) => {
const substitution: string | undefined = args?.[arg as keyof typeof args];
if (isUndefined(substitution)) { if (isUndefined(substitution)) {
const defaultSubstitution = LOCALE_DEFAULTS[arg as keyof typeof LOCALE_DEFAULTS]; const defaultSubstitution = LOCALE_DEFAULTS[arg as keyof typeof LOCALE_DEFAULTS];
return isUndefined(defaultSubstitution) ? match : defaultSubstitution; return isUndefined(defaultSubstitution) ? match : defaultSubstitution;
} }
// TODO: figure out why is was type never and fix the type // TODO: figure out why is was type never and fix the type
return (substitution as string).toString(); return (substitution as string).toString();
}) as R; }
) as R;
} catch (error) { } catch (error) {
i18nLog(`i18n: ${error.message}`); i18nLog(`i18n: ${error.message}`);
return token as R; return token as R;
@ -258,9 +260,36 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => {
window.getLocale = () => locale; window.getLocale = () => locale;
/**
* Retrieves a localized message string, substituting variables where necessary. Then strips the message of any HTML and custom tags.
*
* @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. Any HTML and custom tags are removed.
*
* @example
* // The string greeting is 'Hello, {name}! <b>Welcome!</b>' in the current locale
* window.i18n.stripped('greeting', { name: 'Alice' });
* // => 'Hello, Alice! Welcome!'
*/
getMessage.stripped = <T extends LocalizerToken, R extends LocalizerDictionary[T]>(
...[token, args]: GetMessageArgs<T>
): R => {
const i18nString = getMessage<T, LocalizerDictionary[T]>(
...([token, args] as GetMessageArgs<T>)
);
return i18nString.replaceAll(/<[^>]*>/g, '') as R;
};
return getMessage; return getMessage;
}; };
export const getI18nFunction = (stripTags: boolean) => {
return stripTags ? window.i18n.stripped : window.i18n;
};
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports
export let langNotSupportedMessageShown = false; export let langNotSupportedMessageShown = false;

Loading…
Cancel
Save