Update custom translations to support nested fields in structured JSON (#11685)
* Update matrix-web-i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix custom translations for structured JSON nested fields Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix import Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix export Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update @matrix-org/react-sdk-module-api Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update matrix-web-i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update matrix-web-i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
1897962086
commit
632d8f4bc7
5 changed files with 74 additions and 108 deletions
|
@ -21,7 +21,10 @@ import counterpart from "counterpart";
|
|||
import React from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { MapWithDefault, safeSet } from "matrix-js-sdk/src/utils";
|
||||
import { MapWithDefault } from "matrix-js-sdk/src/utils";
|
||||
import { normalizeLanguageKey, TranslationKey as _TranslationKey, KEY_SEPARATOR } from "matrix-web-i18n";
|
||||
import { TranslationStringsObject } from "@matrix-org/react-sdk-module-api";
|
||||
import _ from "lodash";
|
||||
|
||||
import type Translations from "./i18n/strings/en_EN.json";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
@ -30,11 +33,12 @@ import { SettingLevel } from "./settings/SettingLevel";
|
|||
import { retry } from "./utils/promise";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import { ModuleRunner } from "./modules/ModuleRunner";
|
||||
import { Leaves } from "./@types/common";
|
||||
|
||||
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
|
||||
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
|
||||
|
||||
export { normalizeLanguageKey, getNormalizedLanguageKeys } from "matrix-web-i18n";
|
||||
|
||||
const i18nFolder = "i18n/";
|
||||
|
||||
// Control whether to also return original, untranslated strings
|
||||
|
@ -42,7 +46,7 @@ const i18nFolder = "i18n/";
|
|||
const ANNOTATE_STRINGS = false;
|
||||
|
||||
// We use english strings as keys, some of which contain full stops
|
||||
counterpart.setSeparator("|");
|
||||
counterpart.setSeparator(KEY_SEPARATOR);
|
||||
|
||||
// see `translateWithFallback` for an explanation of fallback handling
|
||||
const FALLBACK_LOCALE = "en";
|
||||
|
@ -110,7 +114,7 @@ export function getUserLanguage(): string {
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
export type TranslationKey = Leaves<typeof Translations, "|", string | { other: string }, 4>;
|
||||
export type TranslationKey = _TranslationKey<typeof Translations>;
|
||||
|
||||
// Function which only purpose is to mark that a string is translatable
|
||||
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
|
||||
|
@ -541,41 +545,6 @@ export function getLanguageFromBrowser(): string {
|
|||
return getLanguagesFromBrowser()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a language string, normalises it,
|
||||
* (see normalizeLanguageKey) into an array of language strings
|
||||
* with fallback to generic languages
|
||||
* (eg. 'pt-BR' => ['pt-br', 'pt'])
|
||||
*
|
||||
* @param {string} language The input language string
|
||||
* @return {string[]} List of normalised languages
|
||||
*/
|
||||
export function getNormalizedLanguageKeys(language: string): string[] {
|
||||
const languageKeys: string[] = [];
|
||||
const normalizedLanguage = normalizeLanguageKey(language);
|
||||
const languageParts = normalizedLanguage.split("-");
|
||||
if (languageParts.length === 2 && languageParts[0] === languageParts[1]) {
|
||||
languageKeys.push(languageParts[0]);
|
||||
} else {
|
||||
languageKeys.push(normalizedLanguage);
|
||||
if (languageParts.length === 2) {
|
||||
languageKeys.push(languageParts[0]);
|
||||
}
|
||||
}
|
||||
return languageKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language string with underscores replaced with
|
||||
* hyphens, and lowercased.
|
||||
*
|
||||
* @param {string} language The language string to be normalized
|
||||
* @returns {string} The normalized language string
|
||||
*/
|
||||
export function normalizeLanguageKey(language: string): string {
|
||||
return language.toLowerCase().replace("_", "-");
|
||||
}
|
||||
|
||||
export function getCurrentLanguage(): string {
|
||||
return counterpart.getLocale();
|
||||
}
|
||||
|
@ -662,34 +631,26 @@ async function getLanguage(langPath: string): Promise<ICounterpartTranslation> {
|
|||
return res.json();
|
||||
}
|
||||
|
||||
export interface ICustomTranslations {
|
||||
// Format is a map of english string to language to override
|
||||
[str: string]: {
|
||||
[lang: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
let cachedCustomTranslations: Optional<ICustomTranslations> = null;
|
||||
let cachedCustomTranslations: Optional<TranslationStringsObject> = null;
|
||||
let cachedCustomTranslationsExpire = 0; // zero to trigger expiration right away
|
||||
|
||||
// This awkward class exists so the test runner can get at the function. It is
|
||||
// not intended for practical or realistic usage.
|
||||
export class CustomTranslationOptions {
|
||||
public static lookupFn?: (url: string) => ICustomTranslations;
|
||||
public static lookupFn?: (url: string) => TranslationStringsObject;
|
||||
|
||||
private constructor() {
|
||||
// static access for tests only
|
||||
}
|
||||
}
|
||||
|
||||
function doRegisterTranslations(customTranslations: ICustomTranslations): void {
|
||||
// We convert the operator-friendly version into something counterpart can
|
||||
// consume.
|
||||
function doRegisterTranslations(customTranslations: TranslationStringsObject): void {
|
||||
// We convert the operator-friendly version into something counterpart can consume.
|
||||
// Map: lang → Record: string → translation
|
||||
const langs: MapWithDefault<string, Record<string, string>> = new MapWithDefault(() => ({}));
|
||||
for (const [str, translations] of Object.entries(customTranslations)) {
|
||||
for (const [lang, newStr] of Object.entries(translations)) {
|
||||
safeSet(langs.getOrCreate(lang), str, newStr);
|
||||
for (const [translationKey, translations] of Object.entries(customTranslations)) {
|
||||
for (const [lang, translation] of Object.entries(translations)) {
|
||||
_.set(langs.getOrCreate(lang), translationKey.split(KEY_SEPARATOR), translation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -719,11 +680,11 @@ export async function registerCustomTranslations({
|
|||
if (!lookupUrl) return; // easy - nothing to do
|
||||
|
||||
try {
|
||||
let json: Optional<ICustomTranslations>;
|
||||
let json: Optional<TranslationStringsObject>;
|
||||
if (testOnlyIgnoreCustomTranslationsCache || Date.now() >= cachedCustomTranslationsExpire) {
|
||||
json = CustomTranslationOptions.lookupFn
|
||||
? CustomTranslationOptions.lookupFn(lookupUrl)
|
||||
: ((await (await fetch(lookupUrl)).json()) as ICustomTranslations);
|
||||
: ((await (await fetch(lookupUrl)).json()) as TranslationStringsObject);
|
||||
cachedCustomTranslations = json;
|
||||
|
||||
// Set expiration to the future, but not too far. Just trying to avoid
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue