mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 08:15:41 +00:00
Merge branch 'feat/do-perf-guard-2' into feat/desktop-offline
This commit is contained in:
@@ -118,7 +118,7 @@ export function DesktopHost() {
|
||||
// Menus and spellcheck follow the interface.
|
||||
useEffect(() => {
|
||||
const ui = uiLocale || document.documentElement.lang;
|
||||
if (ui) setDesktopInterfaceLanguage(ui, shellLabels(tRef.current as unknown as (key: string) => string));
|
||||
if (ui) setDesktopInterfaceLanguage(ui, shellLabels(tRef.current as unknown as Parameters<typeof shellLabels>[0]));
|
||||
}, [uiLocale]);
|
||||
|
||||
// What the user writes in, whichever composer asks for it too.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createTranslator } from 'use-intl/core';
|
||||
import { shellLabels } from '../languages';
|
||||
|
||||
/**
|
||||
* The real catalogs through the real formatter, which the vitest next-intl mock isn't.
|
||||
* A string that needs a value and doesn't get one is an error here. In the app it's a
|
||||
* logged error, and the desktop's menu would show the key instead of the label.
|
||||
*/
|
||||
const LOCALES_DIR = join(process.cwd(), 'locales');
|
||||
const LOCALES = readdirSync(LOCALES_DIR, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
||||
|
||||
function labelsFor(locale: string): { labels: Record<string, string>; errors: string[] } {
|
||||
const messages = JSON.parse(readFileSync(join(LOCALES_DIR, locale, 'common.json'), 'utf8'));
|
||||
const errors: string[] = [];
|
||||
const t = createTranslator({
|
||||
locale,
|
||||
messages,
|
||||
onError: (error) => { errors.push(`${locale}: ${error.message.slice(0, 160)}`); },
|
||||
});
|
||||
return { labels: shellLabels(t as unknown as Parameters<typeof shellLabels>[0]), errors };
|
||||
}
|
||||
|
||||
describe('the labels the desktop shell gets', () => {
|
||||
it('finds every language', () => {
|
||||
expect(LOCALES.length).toBeGreaterThanOrEqual(30);
|
||||
});
|
||||
|
||||
it.each(LOCALES)('%s: every string formats, with no value missing', (locale) => {
|
||||
const { labels, errors } = labelsFor(locale);
|
||||
expect(errors).toEqual([]);
|
||||
for (const [name, label] of Object.entries(labels)) {
|
||||
expect(label, name).not.toMatch(/^[\w-]+(\.[\w-]+)+$/);
|
||||
expect(label.trim(), name).not.toBe('');
|
||||
}
|
||||
});
|
||||
|
||||
it.each(LOCALES)('%s: leaves the shell its placeholders to fill in', (locale) => {
|
||||
const { labels } = labelsFor(locale);
|
||||
expect(labels.contextAddToDictionary).toContain('{word}');
|
||||
expect(labels.unsentMany).toContain('{count}');
|
||||
});
|
||||
});
|
||||
@@ -33,8 +33,12 @@ export function noteComposerLanguage(key: string, language: string | null): void
|
||||
push();
|
||||
}
|
||||
|
||||
/** The shell's text in the interface language, by its keys in src/main/strings.ts. */
|
||||
export function shellLabels(t: (key: string) => string): Record<string, string> {
|
||||
/**
|
||||
* The shell's text in the interface language, by its keys in src/main/strings.ts. The
|
||||
* shell fills in `{word}` and `{count}` itself when it shows them, so they go over as
|
||||
* they are. Without a value next-intl gives back the key, and that would be the label.
|
||||
*/
|
||||
export function shellLabels(t: (key: string, values?: Record<string, string>) => string): Record<string, string> {
|
||||
return {
|
||||
menuFile: t('desktopUi.menu.file'),
|
||||
menuEdit: t('desktopUi.menu.edit'),
|
||||
@@ -99,7 +103,7 @@ export function shellLabels(t: (key: string) => string): Record<string, string>
|
||||
contextCopy: t('desktopUi.menu.copy'),
|
||||
contextPaste: t('desktopUi.menu.paste'),
|
||||
contextSelectAll: t('desktopUi.menu.select_all'),
|
||||
contextAddToDictionary: t('desktopUi.context.add_to_dictionary'),
|
||||
contextAddToDictionary: t('desktopUi.context.add_to_dictionary', { word: '{word}' }),
|
||||
contextNoSuggestions: t('desktopUi.context.no_suggestions'),
|
||||
contextOpenLink: t('desktopUi.context.open_link'),
|
||||
contextCopyLink: t('desktopUi.context.copy_link'),
|
||||
@@ -111,7 +115,7 @@ export function shellLabels(t: (key: string) => string): Record<string, string>
|
||||
closeAnyway: t('desktopUi.closing.close_anyway'),
|
||||
unsentTitle: t('desktopUi.closing.unsent_title'),
|
||||
unsentOne: t('desktopUi.closing.unsent_one'),
|
||||
unsentMany: t('desktopUi.closing.unsent_many'),
|
||||
unsentMany: t('desktopUi.closing.unsent_many', { count: '{count}' }),
|
||||
closeNow: t('desktopUi.closing.quit'),
|
||||
leaveTitle: t('desktopUi.closing.leave_title'),
|
||||
leaveDetail: t('desktopUi.closing.leave_detail'),
|
||||
|
||||
Reference in New Issue
Block a user