fix: still plenty of errors, but simple window.i18n work

pull/3281/head
Audric Ackermann 4 months ago
parent e225d58015
commit 66e80fc1c9
No known key found for this signature in database

@ -719,7 +719,6 @@
"searchContacts": "ค้นหาผู้ติดต่อ", "searchContacts": "ค้นหาผู้ติดต่อ",
"searchConversation": "ค้นหาอะไรในการสนทนา", "searchConversation": "ค้นหาอะไรในการสนทนา",
"searchEnter": "เขียนที่ค้นหา", "searchEnter": "เขียนที่ค้นหา",
"searchMatches": "{count, plural, other [{found_count} จาก # รายการ]}",
"searchMatchesNone": "ไม่พบข้อมูลเลย", "searchMatchesNone": "ไม่พบข้อมูลเลย",
"searchMatchesNoneSpecific": "ไม่พบข้อมูลเกี่ยวกับ '{query}", "searchMatchesNoneSpecific": "ไม่พบข้อมูลเกี่ยวกับ '{query}",
"searchMembers": "ค้นหาสมาชิก", "searchMembers": "ค้นหาสมาชิก",

@ -55,18 +55,11 @@ def extract_vars(text):
vars = re.findall(r'\{(.*?)\}', text) vars = re.findall(r'\{(.*?)\}', text)
return vars return vars
def extract_plurals(text: str) -> List[Tuple[str, str]]:
pattern = r'(\b\w+\b)\s*(\[[^\]]+\])'
matches = re.findall(pattern, text)
return matches
def vars_to_record(vars): def vars_to_record(vars):
arr = [] arr = []
for var in vars: for var in vars:
to_append = '"' + var + '": ' + ('number' if var == 'count' else 'string') to_append = '' + var + ': ' + ('"number"' if var == 'count' or var == 'found_count' else '"string"')
if to_append not in arr: if to_append not in arr:
arr.append(to_append) arr.append(to_append)
@ -100,45 +93,69 @@ def generate_type_object(locales):
str: A string representation of the JavaScript object. str: A string representation of the JavaScript object.
""" """
js_object = "{\n" js_object = "{\n"
plural_pattern = r"(zero|one|two|few|many|other)\s*\[([^\]]+)\]"
for key, value_en in locales['en'].items(): for key, value_en in locales['en'].items():
# print('value',value)
if value_en.startswith("{count, plural, "): if value_en.startswith("{count, plural, "):
continue extracted_vars_en = extract_vars(replaced_en)
# plurals = extract_plurals(value) plurals_other = [[locale, replace_static_strings(data.get(key, ""))] for locale, data in locales.items()]
# print(plurals) en_plurals_with_token = re.findall(plural_pattern, value_en.replace('#', '{count}'))
# js_plural_object = "{\n"
if not en_plurals_with_token:
# for plural in plurals: raise ValueError("invalid plural string")
# plural_token = plural[0]
# plural_str = plural[1].replace('#', '{count}') all_locales_plurals = []
# extracted_vars = extract_vars(replace_static_strings(plural_str))
# if('count' not in extracted_vars): extracted_vars = extract_vars(replace_static_strings(en_plurals_with_token[0][1]))
# extracted_vars.append('count') if('count' not in extracted_vars):
# print('extracted_vars',extracted_vars) extracted_vars.append('count')
# as_record_type = vars_to_record(extracted_vars) for plural in plurals_other:
# js_plural_object += f" {wrapValue(plural_token)}: {as_record_type},\n" js_plural_object = ""
# js_plural_object += " }"
# js_object += f" {wrapValue(key)}: {js_plural_object},\n" locale_key = plural[0] # 'lo', 'th', ....
plural_str = plural[1].replace('#', '{count}')
plurals_with_token = re.findall(plural_pattern, plural_str)
all_locales_strings = []
as_record_type_en = vars_to_record(extracted_vars)
for token, localized_string in plurals_with_token:
if localized_string:
to_append = ""
to_append += token
to_append += f": \"{localized_string.replace("\n", "\\n")}\""
all_locales_strings.append(to_append)
# if that locale doesn't have translation in plurals, add the english hones
if not len(all_locales_strings):
for plural_en_token, plural_en_str in en_plurals_with_token:
all_locales_strings.append(f"{plural_en_token}: \"{plural_en_str.replace("\n", "\\n")}\"")
js_plural_object += f" {wrapValue(locale_key)}:"
js_plural_object += "{\n "
js_plural_object += ",\n ".join(all_locales_strings)
js_plural_object += "\n },"
all_locales_plurals.append(js_plural_object)
js_object += f" {wrapValue(key)}: {{\n{"\n".join(all_locales_plurals)}\n args: {f"{as_record_type_en} as const," if as_record_type_en else 'undefined,'}\n }},\n"
else: else:
replaced_en = replace_static_strings(value_en) replaced_en = replace_static_strings(value_en)
extracted_vars = extract_vars(replaced_en) extracted_vars_en = extract_vars(replaced_en)
as_record_type = vars_to_record(extracted_vars) as_record_type_en = vars_to_record(extracted_vars_en)
other_locales_replaced_values = [[locale, replace_static_strings(data.get(key, ""))] for locale, data in locales.items()] other_locales_replaced_values = [[locale, replace_static_strings(data.get(key, ""))] for locale, data in locales.items()]
filtered_values = [] all_locales_strings = []
# filter out strings that matches the english one (i.e. untranslated strings)
for locale, replaced_val in other_locales_replaced_values: for locale, replaced_val in other_locales_replaced_values:
if replaced_val == replaced_en and not locale == 'en': if replaced_val:
# print(f"{locale}: ${key} saved content is the same as english saved.") all_locales_strings.append(f"{locale}: \"{replaced_val.replace("\n", "\\n")}\"")
filtered_values.append(f"{locale}: undefined")
else: else:
filtered_values.append(f"{locale}: \"{replaced_val}\"") all_locales_strings.append(f"{locale}: \"{replaced_en.replace("\n", "\\n")}\"")
# print('key',key, " other_locales_replaced_values:", other_locales_replaced_values) # print('key',key, " other_locales_replaced_values:", other_locales_replaced_values)
js_object += f" {wrapValue(key)}:{{\n {",\n ".join(filtered_values)},\n args: {as_record_type if as_record_type else 'undefined'}\n }},\n" js_object += f" {wrapValue(key)}: {{\n {",\n ".join(all_locales_strings)},\n args: {f"{as_record_type_en} as const," if as_record_type_en else 'undefined,'}\n }},\n"
js_object += "}" js_object += "}"
return js_object return js_object
@ -189,7 +206,7 @@ def generateLocalesMergedType(locales):
) )
ts_file.write( ts_file.write(
f"export const plop = {generate_type_object(locales)};\n" f"export const dictionary = {generate_type_object(locales)};\n\nexport type Dictionary = typeof dictionary;\n"
) )
return f"Locales generated at: {OUTPUT_FILE}" return f"Locales generated at: {OUTPUT_FILE}"

@ -13,7 +13,8 @@ export const testDictionary = {
export function initI18n(dictionary: Record<string, string> = en) { export function initI18n(dictionary: Record<string, string> = en) {
return setupI18n({ return setupI18n({
// testing
crowdinLocale: 'en', crowdinLocale: 'en',
translationDictionary: dictionary as LocalizerDictionary, translationDictionary: dictionary as LocalizerDictionary, // testing
}); });
} }

@ -1,6 +1,5 @@
import type { ElementType } from 'react'; import type { ElementType } from 'react';
import type { Dictionary } from '../localization/locales'; import type { Dictionary } from '../localization/locales';
import type { LOCALE_DEFAULTS } from '../localization/constants';
/** The dictionary of localized strings */ /** The dictionary of localized strings */
export type LocalizerDictionary = Dictionary; export type LocalizerDictionary = Dictionary;
@ -9,7 +8,8 @@ export type LocalizerDictionary = Dictionary;
export type LocalizerToken = keyof Dictionary; export type LocalizerToken = keyof Dictionary;
/** A dynamic argument that can be used in a localized string */ /** A dynamic argument that can be used in a localized string */
export type DynamicArg = string | number; type DynamicArg = string | number;
type DynamicArgStr = 'string' | 'number';
/** A record of dynamic arguments for a specific key in the localization dictionary */ /** A record of dynamic arguments for a specific key in the localization dictionary */
export type ArgsRecord<T extends LocalizerToken> = Record<DynamicArgs<Dictionary[T]>, DynamicArg>; export type ArgsRecord<T extends LocalizerToken> = Record<DynamicArgs<Dictionary[T]>, DynamicArg>;
@ -19,32 +19,33 @@ export type DictionaryWithoutPluralStrings = Dictionary;
export type PluralKey = 'count'; export type PluralKey = 'count';
export type PluralString = `{${string}, plural, one [${string}] other [${string}]}`; export type PluralString = `{${string}, plural, one [${string}] other [${string}]}`;
/** The dynamic arguments in a localized string */ type ArgsTypeStrToTypes<T extends DynamicArgStr> = T extends 'string'
type DynamicArgs<LocalizedString extends string> = ? string
/** If a string follows the plural format use its plural variable name and recursively check for : T extends 'number'
* dynamic args inside all plural forms */ ? number
LocalizedString extends `{${infer PluralVar}, plural, one [${infer PluralOne}] other [${infer PluralOther}]}` : never;
? PluralVar | DynamicArgs<PluralOne> | DynamicArgs<PluralOther>
: /** If a string segment follows the variable form parse its variable name and recursively
* check for more dynamic args */
LocalizedString extends `${string}{${infer Var}}${infer Rest}`
? Var | DynamicArgs<Rest>
: never;
export type ArgsRecordExcludingDefaults<T extends LocalizerToken> = Omit< // those are still a string of the type "string" | "number" and not the typescript types themselves
ArgsRecord<T>, type ArgsFromTokenStr<T extends LocalizerToken> = Dictionary[T]['args'] extends undefined
keyof typeof LOCALE_DEFAULTS ? never
>; : Dictionary[T]['args'];
type ArgsFromToken<T extends LocalizerToken> = MappedToTsTypes<ArgsFromTokenStr<T>>;
type IsTokenWithCountArgs<T extends LocalizerToken> = 'count' extends keyof ArgsFromToken<T>
? true
: false;
/** The arguments for retrieving a localized message */ /** The arguments for retrieving a localized message */
export type GetMessageArgs<T extends LocalizerToken> = T extends LocalizerToken export type GetMessageArgs<T extends LocalizerToken> = T extends LocalizerToken
? DynamicArgs<Dictionary[T]> extends never ? ArgsFromToken<T> extends never
? [T] ? [T]
: ArgsRecordExcludingDefaults<T> extends Record<string, never> : [T, ArgsFromToken<T>]
? [T]
: [T, ArgsRecordExcludingDefaults<T>]
: never; : never;
type MappedToTsTypes<T extends Record<string, DynamicArgStr>> = {
[K in keyof T]: ArgsTypeStrToTypes<T[K]>;
};
/** Basic props for all calls of the Localizer component */ /** Basic props for all calls of the Localizer component */
type LocalizerComponentBaseProps<T extends LocalizerToken> = { type LocalizerComponentBaseProps<T extends LocalizerToken> = {
token: T; token: T;
@ -54,11 +55,11 @@ type LocalizerComponentBaseProps<T extends LocalizerToken> = {
/** The props for the localization component */ /** The props for the localization component */
export type LocalizerComponentProps<T extends LocalizerToken> = T extends LocalizerToken export type LocalizerComponentProps<T extends LocalizerToken> = T extends LocalizerToken
? DynamicArgs<Dictionary[T]> extends never ? ArgsFromToken<T> extends never
? LocalizerComponentBaseProps<T> ? LocalizerComponentBaseProps<T>
: ArgsRecordExcludingDefaults<T> extends Record<string, never> : ArgsFromToken<T> extends Record<string, never>
? LocalizerComponentBaseProps<T> ? LocalizerComponentBaseProps<T>
: LocalizerComponentBaseProps<T> & { args: ArgsRecordExcludingDefaults<T> } : LocalizerComponentBaseProps<T> & { args: ArgsFromToken<T> }
: never; : never;
export type LocalizerComponentPropsObject = LocalizerComponentProps<LocalizerToken>; export type LocalizerComponentPropsObject = LocalizerComponentProps<LocalizerToken>;

@ -29,6 +29,7 @@ export const setupI18n = ({
if (!translationDictionary || isEmpty(translationDictionary)) { if (!translationDictionary || isEmpty(translationDictionary)) {
throw new Error('translationDictionary was not provided'); throw new Error('translationDictionary was not provided');
} }
console.warn('translationDictionary', translationDictionary);
setInitialLocale(crowdinLocale, translationDictionary); setInitialLocale(crowdinLocale, translationDictionary);

@ -221,7 +221,7 @@ function update(forceRefresh = false) {
if (shouldHideExpiringMessageBody) { if (shouldHideExpiringMessageBody) {
message = window.i18n('messageNew', { count: messagesNotificationCount }); message = window.i18n('messageNew', { count: messagesNotificationCount });
} }
window.drawAttention(); window.drawAttention();
if (status.shouldPlayNotificationSound) { if (status.shouldPlayNotificationSound) {
if (!sound) { if (!sound) {

9
ts/window.d.ts vendored

@ -5,12 +5,7 @@ import { Store } from '@reduxjs/toolkit';
import { Persistor } from 'redux-persist/es/types'; import { Persistor } from 'redux-persist/es/types';
import { PrimaryColorStateType, ThemeStateType } from './themes/constants/colors'; import { PrimaryColorStateType, ThemeStateType } from './themes/constants/colors';
import type { import type { GetMessageArgs, I18nMethods, LocalizerToken } from './types/localizer';
GetMessageArgs,
I18nMethods,
LocalizerDictionary,
LocalizerToken,
} from './types/localizer';
export interface LibTextsecure { export interface LibTextsecure {
messaging: boolean; messaging: boolean;
@ -50,7 +45,7 @@ declare global {
* window.i18n('search', { count: 1, found_count: 1 }); * window.i18n('search', { count: 1, found_count: 1 });
* // => '1 of 1 match' * // => '1 of 1 match'
*/ */
i18n: (<T extends LocalizerToken, R extends LocalizerDictionary[T]>( i18n: (<T extends LocalizerToken, R extends string>(
...[token, args]: GetMessageArgs<T> ...[token, args]: GetMessageArgs<T>
) => R) & { ) => R) & {
/** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.getRawMessage } and {@link window.i18n.getRawMessage } */ /** NOTE: Because of docstring limitations changes MUST be manually synced between {@link setupI18n.getRawMessage } and {@link window.i18n.getRawMessage } */

Loading…
Cancel
Save