Skip to content
Snippets Groups Projects
Unverified Commit b9795304 authored by Tasso Evangelista's avatar Tasso Evangelista Committed by GitHub
Browse files

regression(i18n): Broken translations on client-side startup (#29442)

parent b31ccd4a
No related branches found
No related tags found
No related merge requests found
......@@ -180,8 +180,8 @@ class E2E extends Emitter {
this.started = false;
failedToDecodeKey = true;
this.openAlert({
title: t("Wasn't possible to decode your encryption key to be imported."),
html: '<div>Your encryption password seems wrong. Click here to try again.</div>',
title: "Wasn't possible to decode your encryption key to be imported.", // TODO: missing translation
html: '<div>Your encryption password seems wrong. Click here to try again.</div>', // TODO: missing translation
modifiers: ['large', 'danger'],
closable: true,
icon: 'key',
......@@ -212,8 +212,8 @@ class E2E extends Emitter {
});
this.openAlert({
title: t('Save_your_encryption_password'),
html: t('Click_here_to_view_and_copy_your_password'),
title: () => t('Save_your_encryption_password'),
html: () => t('Click_here_to_view_and_copy_your_password'),
modifiers: ['large'],
closable: false,
icon: 'key',
......@@ -381,8 +381,8 @@ class E2E extends Emitter {
const showAlert = () => {
this.openAlert({
title: t('Enter_your_E2E_password'),
html: t('Click_here_to_enter_your_encryption_password'),
title: () => t('Enter_your_E2E_password'),
html: () => t('Click_here_to_enter_your_encryption_password'),
modifiers: ['large'],
closable: false,
icon: 'key',
......
......@@ -6,9 +6,9 @@ import type { ComponentProps } from 'react';
export type LegacyBannerPayload = {
id: string;
closable?: boolean;
title?: string;
text?: string;
html?: string;
title?: string | (() => string);
text?: string | (() => string);
html?: string | (() => string);
icon?: ComponentProps<typeof Icon>['name'];
modifiers?: ('large' | 'danger')[];
timer?: number;
......
......@@ -39,69 +39,73 @@ const useI18next = (lng: string): typeof i18next => {
const customTranslations = useSetting('Custom_Translations');
const parse = useMutableCallback((data: string, lngs?: string | string[], namespaces: string | string[] = []): { [key: string]: any } => {
const parsedCustomTranslations = typeof customTranslations === 'string' && parseToJSON(customTranslations);
const parsedCustomTranslations = useMemo(() => {
if (!customTranslations || typeof customTranslations !== 'string') {
return;
}
const source = JSON.parse(data);
const result: { [key: string]: any } = {};
return parseToJSON(customTranslations);
}, [customTranslations]);
for (const [key, value] of Object.entries(source)) {
const [prefix] = key.split('.');
const extractKeys = useMutableCallback(
(source: Record<string, string>, lngs?: string | string[], namespaces: string | string[] = []): { [key: string]: any } => {
const result: { [key: string]: any } = {};
if (prefix && Array.isArray(namespaces) ? namespaces.includes(prefix) : prefix === namespaces) {
result[key.slice(prefix.length + 1)] = value;
continue;
}
for (const [key, value] of Object.entries(source)) {
const [prefix] = key.split('.');
if (Array.isArray(namespaces) ? namespaces.includes('core') : namespaces === 'core') {
result[key] = value;
}
}
if (lngs && parsedCustomTranslations) {
for (const language of Array.isArray(lngs) ? lngs : [lngs]) {
if (!parsedCustomTranslations[language]) {
if (prefix && Array.isArray(namespaces) ? namespaces.includes(prefix) : prefix === namespaces) {
result[key.slice(prefix.length + 1)] = value;
continue;
}
for (const [key, value] of Object.entries(parsedCustomTranslations[language])) {
const prefix = (Array.isArray(namespaces) ? namespaces : [namespaces]).find((namespace) => key.startsWith(`${namespace}.`));
if (Array.isArray(namespaces) ? namespaces.includes('core') : namespaces === 'core') {
result[key] = value;
}
}
if (lngs && parsedCustomTranslations) {
for (const language of Array.isArray(lngs) ? lngs : [lngs]) {
if (!parsedCustomTranslations[language]) {
continue;
}
for (const [key, value] of Object.entries(parsedCustomTranslations[language])) {
const prefix = (Array.isArray(namespaces) ? namespaces : [namespaces]).find((namespace) => key.startsWith(`${namespace}.`));
if (prefix) {
result[key.slice(prefix.length + 1)] = value;
if (prefix) {
result[key.slice(prefix.length + 1)] = value;
}
}
}
}
}
return result;
});
const instance = useMemo(
() =>
i18n.init({
lng,
fallbackLng: 'en',
ns: namespacesDefault,
nsSeparator: '.',
resources: {
en: {
core: en,
},
},
partialBundledLanguages: true,
defaultNS: 'core',
backend: {
loadPath: `${basePath}/{{lng}}.json`,
parse,
},
react: {
useSuspense: true,
},
}),
[lng, basePath, parse],
return result;
},
);
if (!i18n.isInitialized) {
i18n.init({
lng,
fallbackLng: 'en',
ns: namespacesDefault,
nsSeparator: '.',
resources: {
en: extractKeys(en),
},
partialBundledLanguages: true,
defaultNS: 'core',
backend: {
loadPath: `${basePath}/{{lng}}.json`,
parse: (data: string, lngs?: string | string[], namespaces: string | string[] = []) =>
extractKeys(JSON.parse(data), lngs, namespaces),
},
react: {
useSuspense: true,
},
});
}
useEffect(() => {
if (i18n.language !== lng) {
i18n.changeLanguage(lng);
......@@ -109,12 +113,6 @@ const useI18next = (lng: string): typeof i18next => {
}, [lng]);
useEffect(() => {
if (!customTranslations || typeof customTranslations !== 'string') {
return;
}
const parsedCustomTranslations = parseToJSON(customTranslations);
if (!parsedCustomTranslations) {
return;
}
......@@ -141,7 +139,7 @@ const useI18next = (lng: string): typeof i18next => {
i18n.addResourceBundle(ln, namespace, translations);
}
}
}, [customTranslations, instance]);
}, [parsedCustomTranslations]);
return i18n;
};
......
......@@ -77,8 +77,8 @@ Meteor.startup(() => {
if (connectToCloud === true && workspaceRegistered !== true) {
banners.open({
id: 'cloud-registration',
title: t('Cloud_registration_pending_title'),
html: t('Cloud_registration_pending_html'),
title: () => t('Cloud_registration_pending_title'),
html: () => t('Cloud_registration_pending_html'),
modifiers: ['large', 'danger'],
});
}
......
......@@ -45,13 +45,13 @@ const LegacyBanner: FC<LegacyBannerProps> = ({ config }) => {
actionable={!!config.action}
closeable={closable}
icon={icon ? <Icon name={icon} size={20} /> : undefined}
title={title}
title={typeof title === 'function' ? title() : title}
variant={variant}
onAction={handleAction}
onClose={handleClose}
>
{text}
{html && <div dangerouslySetInnerHTML={{ __html: html }} />}
{typeof text === 'function' ? text() : text}
{html && <div dangerouslySetInnerHTML={{ __html: typeof html === 'function' ? html() : html }} />}
</Banner>
);
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment