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

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

@ -14,6 +14,7 @@ import {
import timeLocales from 'date-fns/locale';
import { isUndefined } from 'lodash';
import {
DictionaryWithoutPluralStrings,
GetMessageArgs,
LocalizerDictionary,
LocalizerToken,
@ -23,6 +24,7 @@ import {
import { DURATION_SECONDS, LOCALE_DEFAULTS } from '../session/constants';
import { updateLocale } from '../state/ducks/dictionary';
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { Dictionary } from '../localization/locales';
export function loadDictionary(locale: Locale) {
return import(`../../_locales/${locale}/messages.json`) as Promise<LocalizerDictionary>;
@ -98,32 +100,30 @@ const timeLocaleMap = {
export type Locale = keyof typeof timeLocaleMap;
const enPluralFormRegex = /\{(\w+), plural, one \{(\w+)\} other \{(\w+)\}\}/;
const cardinalPluralRegex: Record<Intl.LDMLPluralRule, RegExp> = {
zero: /(zero) \{([^}]*)\}/,
one: /(one) \{([^}]*)\}/,
two: /(two) \{([^}]*)\}/,
few: /(few) \{([^}]*)\}/,
many: /(many) \{([^}]*)\}/,
other: /(other) \{([^}]*)\}/,
zero: /zero \[(.*?)\]/g,
one: /one \[(.*?)\]/g,
two: /two \[(.*?)\]/g,
few: /few \[(.*?)\]/g,
many: /many \[(.*?)\]/g,
other: /other \[(.*?)\]/g,
};
function getPluralKey(string: PluralString): PluralKey | undefined {
const match = string.match(enPluralFormRegex);
return match && match[1] ? match[1] : undefined;
function getPluralKey<R extends PluralKey | undefined>(string: PluralString): R {
const match = /{(\w+), plural, one \[.+\] other \[.+\]}/g.exec(string);
return (match?.[1] ?? undefined) as R;
}
function getStringForCardinalRule(
localizedString: string,
cardinalRule: Intl.LDMLPluralRule
): string | undefined {
const match = localizedString.match(cardinalPluralRegex[cardinalRule]);
return match ? match[2] : undefined;
const match = cardinalPluralRegex[cardinalRule].exec(localizedString);
return match?.[1] ?? undefined;
}
const isPluralForm = (localizedString: string): localizedString is PluralString =>
enPluralFormRegex.test(localizedString);
/{\w+, plural, one \[.+\] other \[.+\]}/g.test(localizedString);
/**
* 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 */
// @ts-expect-error TODO: Fix this type, now that we have plurals it doesnt quite work
return localizedString.replace(/\{(\w+)\}/g, (match, arg: keyof typeof args) => {
const substitution: string | undefined = args?.[arg];
return (localizedString as DictionaryWithoutPluralStrings[T]).replace(
/\{(\w+)\}/g,
(match, arg) => {
const substitution: string | undefined = args?.[arg as keyof typeof args];
if (isUndefined(substitution)) {
const defaultSubstitution = LOCALE_DEFAULTS[arg as keyof typeof LOCALE_DEFAULTS];
if (isUndefined(substitution)) {
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
return (substitution as string).toString();
}) as R;
// TODO: figure out why is was type never and fix the type
return (substitution as string).toString();
}
) as R;
} catch (error) {
i18nLog(`i18n: ${error.message}`);
return token as R;
@ -258,9 +260,36 @@ export const setupi18n = (locale: Locale, dictionary: LocalizerDictionary) => {
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;
};
export const getI18nFunction = (stripTags: boolean) => {
return stripTags ? window.i18n.stripped : window.i18n;
};
// eslint-disable-next-line import/no-mutable-exports
export let langNotSupportedMessageShown = false;

Loading…
Cancel
Save