Files
inbuxa-webmail/web/src/lib/__tests__/languages.test.ts
T
jcoffey-dev 53c38ed3c3 French, on the same terms as German
Second of Phase 1. Generated by AI, unreviewed, marked Beta, with the report
link in Settings doing the job a native speaker would otherwise do. 781 of 796
strings; the fifteen left are product names, bare URLs and example addresses,
which should stay English.

Register is "vous", following the "Sie" decision and for the same reason: a
mail client a workplace deployed has no business addressing anybody as "tu".

One terminology decision goes the opposite way to German, deliberately.
"Label" stays English in German because no German client translates it, and
becomes "Libellé" in French because Gmail did and a French reader will meet it
there. The rule being followed is "use what the reader will find elsewhere",
not "always translate" or "never" -- which only looks inconsistent if the rule
is mistaken for the outcome.

French typography: the narrow no-break space before ? ! and : is the rule and
is deliberately not used. It is invisible in a diff, trivially lost in an
editor, and no French webmail actually ships it. Guillemets are used, because
those are visible and do read as wrong when missing.

Two things worth recording from doing this a second time.

The folder-context separator arrived as a raw U+0004 byte rather than the
\\u0004 escape the German file uses. It would have worked -- TypeScript accepts
it -- and it is invisible in an editor and in a diff, which is exactly why the
German catalogue writes it as an escape. Converted, so both files say the same
thing in the same way.

And the language tests named a real language as their example of one that is
not shipped, so shipping German broke them, and shipping French broke them
again. Both times the failure was the test being out of date rather than
anything wrong. They derive an unshipped tag now, and assert over every shipped
language rather than a hardcoded pair, so the third and fourth languages will
not repeat it.
2026-08-31 11:44:40 -07:00

61 lines
2.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { DEFAULT_UI_LANGUAGE, UI_LANGUAGES, resolveUiLanguage } from "@/lib/languages";
import { DEFAULT_SETTINGS, acceptRemote } from "@/store/settings";
/**
* The interface language decides what `<html lang>` claims, and a wrong claim
* is exactly what makes Chrome offer to translate a page that needs no
* translating — which is the offer that ends in a rewritten DOM and a crashed
* component tree. So the resolution is deliberately narrow.
*/
describe("resolveUiLanguage", () => {
it("is English when nothing has been chosen", () => {
// The absent case covers both a new account and every settings file
// written before this setting existed.
expect(resolveUiLanguage(undefined)).toBe("en");
expect(resolveUiLanguage(null)).toBe("en");
expect(resolveUiLanguage("")).toBe("en");
expect(DEFAULT_SETTINGS.uiLanguage).toBe(DEFAULT_UI_LANGUAGE);
});
it("refuses a language whose strings are not shipped", () => {
// The account travels between machines and can outlive a catalogue. A
// page that says lang="fr" while rendering English is worse than one that
// admits to English: it stops the reader translating it themselves.
// Derived rather than named, so shipping another language does not turn
// this into a failing test that is really just out of date.
const unshipped = ["cy", "is", "mt", "eu"].find((tag) => !UI_LANGUAGES.some((l) => l.tag === tag))!;
expect(resolveUiLanguage(unshipped)).toBe("en");
expect(resolveUiLanguage("xx-XX")).toBe("en");
});
it("carries the Beta flag until a person has signed the language off", () => {
// Not a completeness measure. A catalogue can be word-for-word finished
// and still read like a machine wrote it, which is what this marks.
// Every shipped language except English is unreviewed, and stays marked
// until a person says otherwise.
for (const l of UI_LANGUAGES) {
if (l.tag === "en") expect(l.beta).toBeUndefined();
else expect(l.beta).toBe(true);
}
});
it("honours one that is", () => {
for (const l of UI_LANGUAGES) expect(resolveUiLanguage(l.tag)).toBe(l.tag);
});
it("only offers languages that resolve to themselves", () => {
// Guards the ordering mistake: adding a picker entry before its catalogue.
for (const l of UI_LANGUAGES) {
expect(resolveUiLanguage(l.tag)).toBe(l.tag);
expect(l.name.trim()).not.toBe("");
}
});
it("follows the account rather than the device", () => {
// Language is a preference about the person, not the screen: it is not in
// DEVICE_KEYS, so it rides in the settings file like the rest.
expect(acceptRemote({ uiLanguage: "en" })).toEqual({ uiLanguage: "en" });
});
});