Change i18n from singleton to functions

This commit is contained in:
Henrik Giesel 2021-10-03 03:26:19 +02:00
parent 58a4190abb
commit 91c9154ea3
6 changed files with 74 additions and 65 deletions

View file

@ -29,6 +29,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
} }
/// Treat text like HTML, merging multiple spaces and converting
/// newlines to spaces.
export function collapseWhitespace(s: string): string {
return s.replace(/\s+/g, " ");
}
function removeConfig(): void { function removeConfig(): void {
// show pop-up after dropdown has gone away // show pop-up after dropdown has gone away
setTimeout(() => { setTimeout(() => {
@ -41,7 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
? tr.deckConfigWillRequireFullSync() + " " ? tr.deckConfigWillRequireFullSync() + " "
: "") + : "") +
tr.deckConfigConfirmRemoveName({ name: state.getCurrentName() }); tr.deckConfigConfirmRemoveName({ name: state.getCurrentName() });
if (confirm(tr.i18n.withCollapsedWhitespace(msg))) { if (confirm(collapseWhitespace(msg))) {
try { try {
state.removeCurrentConfig(); state.removeCurrentConfig();
} catch (err) { } catch (err) {

View file

@ -9,7 +9,7 @@ import { DeckConfig } from "../lib/proto";
import { postRequest } from "../lib/postrequest"; import { postRequest } from "../lib/postrequest";
import { Writable, writable, get, Readable, readable } from "svelte/store"; import { Writable, writable, get, Readable, readable } from "svelte/store";
import { isEqual, cloneDeep } from "lodash-es"; import { isEqual, cloneDeep } from "lodash-es";
import { i18n } from "../lib/i18n"; import { localeCompare } from "../lib/i18n";
import type { DynamicSvelteComponent } from "../sveltelib/dynamicComponent"; import type { DynamicSvelteComponent } from "../sveltelib/dynamicComponent";
export async function getDeckOptionsInfo( export async function getDeckOptionsInfo(
@ -278,9 +278,7 @@ export class DeckOptionsState {
useCount, useCount,
}; };
}); });
list.sort((a, b) => list.sort((a, b) => localeCompare(a.name, b.name, { sensitivity: "base" }));
a.name.localeCompare(b.name, i18n.langs, { sensitivity: "base" })
);
return list; return list;
} }

View file

@ -4,12 +4,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="typescript"> <script lang="typescript">
import type { TableDatum } from "./graph-helpers"; import type { TableDatum } from "./graph-helpers";
import { i18n } from "../lib/i18n"; import { direction } from "../lib/i18n";
export let tableData: TableDatum[]; export let tableData: TableDatum[];
</script> </script>
<div> <div>
<table dir={i18n.direction()}> <table dir={direction()}>
{#each tableData as { label, value }} {#each tableData as { label, value }}
<tr> <tr>
<td class="align-end">{label}:</td> <td class="align-end">{label}:</td>

View file

@ -29,7 +29,7 @@ import {
SearchDispatch, SearchDispatch,
} from "./graph-helpers"; } from "./graph-helpers";
import { clickableClass } from "./graph-styles"; import { clickableClass } from "./graph-styles";
import { tr, i18n } from "../lib/i18n"; import { tr, weekdayLabel, toLocaleString } from "../lib/i18n";
export interface GraphData { export interface GraphData {
// indexed by day, where day is relative to today // indexed by day, where day is relative to today
@ -154,7 +154,7 @@ export function renderCalendar(
.interpolator((n) => interpolateBlues(cappedRange(n)!)); .interpolator((n) => interpolateBlues(cappedRange(n)!));
function tooltipText(d: DayDatum): string { function tooltipText(d: DayDatum): string {
const date = d.date.toLocaleString(i18n.langs, { const date = toLocaleString(d.date, {
weekday: "long", weekday: "long",
year: "numeric", year: "numeric",
month: "long", month: "long",
@ -171,7 +171,7 @@ export function renderCalendar(
.selectAll("text") .selectAll("text")
.data(sourceData.weekdayLabels) .data(sourceData.weekdayLabels)
.join("text") .join("text")
.text((d: number) => i18n.weekdayLabel(d)) .text((d: number) => weekdayLabel(d))
.attr("width", x(-1)! - 2) .attr("width", x(-1)! - 2)
.attr("height", height - 2) .attr("height", height - 2)
.attr("x", x(1)! - 3) .attr("x", x(1)! - 3)

View file

@ -18,7 +18,7 @@ class Variable(TypedDict):
def methods() -> str: def methods() -> str:
out = [ out = [
'import { i18n } from "./i18n";', 'import { translate } from "./i18n";',
] ]
for module in modules: for module in modules:
for translation in module["translations"]: for translation in module["translations"]:
@ -30,7 +30,7 @@ def methods() -> str:
f""" f"""
/** {doc} */ /** {doc} */
export function {key}({arg_types}): string {{ export function {key}({arg_types}): string {{
return i18n.translate("{translation["key"]}"{args}) return translate("{translation["key"]}"{args})
}} }}
""" """
) )

View file

@ -6,13 +6,11 @@
import "intl-pluralrules"; import "intl-pluralrules";
import { FluentBundle, FluentResource, FluentNumber } from "@fluent/bundle"; import { FluentBundle, FluentResource, FluentNumber } from "@fluent/bundle";
import type { ModuleName } from "./i18n-modules";
type RecordVal = number | string | FluentNumber; type RecordVal = number | string | FluentNumber;
function formatNumbers(args?: Record<string, RecordVal>): void { function formatNumbers(args: Record<string, RecordVal>): void {
if (!args) {
return;
}
for (const key of Object.keys(args)) { for (const key of Object.keys(args)) {
if (typeof args[key] === "number") { if (typeof args[key] === "number") {
args[key] = new FluentNumber(args[key] as number, { args[key] = new FluentNumber(args[key] as number, {
@ -22,13 +20,14 @@ function formatNumbers(args?: Record<string, RecordVal>): void {
} }
} }
export class I18n { let bundles: FluentBundle[] = [];
bundles: FluentBundle[] = [];
langs: string[] = [];
translate(key: string, args?: Record<string, RecordVal>): string { export function translate(key: string, args?: Record<string, RecordVal>): string {
if (args) {
formatNumbers(args); formatNumbers(args);
for (const bundle of this.bundles) { }
for (const bundle of bundles) {
const msg = bundle.getMessage(key); const msg = bundle.getMessage(key);
if (msg && msg.value) { if (msg && msg.value) {
return bundle.formatPattern(msg.value, args); return bundle.formatPattern(msg.value, args);
@ -37,8 +36,8 @@ export class I18n {
return `missing key: ${key}`; return `missing key: ${key}`;
} }
supportsVerticalText(): boolean { export function supportsVerticalText(): boolean {
const firstLang = this.bundles[0].locales[0]; const firstLang = bundles[0].locales[0];
return ( return (
firstLang.startsWith("ja") || firstLang.startsWith("ja") ||
firstLang.startsWith("zh") || firstLang.startsWith("zh") ||
@ -46,8 +45,8 @@ export class I18n {
); );
} }
direction(): string { export function direction(): string {
const firstLang = this.bundles[0].locales[0]; const firstLang = bundles[0].locales[0];
if ( if (
firstLang.startsWith("ar") || firstLang.startsWith("ar") ||
firstLang.startsWith("he") || firstLang.startsWith("he") ||
@ -59,8 +58,8 @@ export class I18n {
} }
} }
weekdayLabel(n: number): string { export function weekdayLabel(n: number): string {
const firstLang = this.bundles[0].locales[0]; const firstLang = bundles[0].locales[0];
const now = new Date(); const now = new Date();
const daysFromToday = -now.getDay() + n; const daysFromToday = -now.getDay() + n;
const desiredDay = new Date(now.getTime() + daysFromToday * 86_400_000); const desiredDay = new Date(now.getTime() + daysFromToday * 86_400_000);
@ -69,17 +68,22 @@ export class I18n {
}); });
} }
/// Treat text like HTML, merging multiple spaces and converting let langs: string[] = [];
/// newlines to spaces.
withCollapsedWhitespace(s: string): string { export function toLocaleString(
return s.replace(/\s+/g, " "); date: Date,
} options?: Intl.DateTimeFormatOptions
): string {
return date.toLocaleDateString(langs, options);
} }
// global singleton export function localeCompare(
export const i18n = new I18n(); first: string,
second: string,
import type { ModuleName } from "./i18n-modules"; options?: Intl.CollatorOptions
): number {
return first.localeCompare(second, langs, options);
}
export async function setupI18n(args: { modules: ModuleName[] }): Promise<void> { export async function setupI18n(args: { modules: ModuleName[] }): Promise<void> {
const resp = await fetch("/_anki/i18nResources", { const resp = await fetch("/_anki/i18nResources", {
@ -91,16 +95,17 @@ export async function setupI18n(args: { modules: ModuleName[] }): Promise<void>
} }
const json = await resp.json(); const json = await resp.json();
i18n.bundles = []; bundles = [];
for (const i in json.resources) { for (const i in json.resources) {
const text = json.resources[i]; const text = json.resources[i];
const lang = json.langs[i]; const lang = json.langs[i];
const bundle = new FluentBundle([lang, "en-US"]); const bundle = new FluentBundle([lang, "en-US"]);
const resource = new FluentResource(text); const resource = new FluentResource(text);
bundle.addResource(resource); bundle.addResource(resource);
i18n.bundles.push(bundle); bundles.push(bundle);
} }
i18n.langs = json.langs;
langs = json.langs;
} }
export { ModuleName } from "./i18n-modules"; export { ModuleName } from "./i18n-modules";