mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Change i18n from singleton to functions
This commit is contained in:
parent
58a4190abb
commit
91c9154ea3
6 changed files with 74 additions and 65 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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})
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,32 +20,33 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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") ||
|
||||||
firstLang.startsWith("ko")
|
firstLang.startsWith("ko")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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") ||
|
||||||
|
@ -57,29 +56,34 @@ export class I18n {
|
||||||
} else {
|
} else {
|
||||||
return "ltr";
|
return "ltr";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
return desiredDay.toLocaleDateString(firstLang, {
|
return desiredDay.toLocaleDateString(firstLang, {
|
||||||
weekday: "narrow",
|
weekday: "narrow",
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/// Treat text like HTML, merging multiple spaces and converting
|
|
||||||
/// newlines to spaces.
|
|
||||||
withCollapsedWhitespace(s: string): string {
|
|
||||||
return s.replace(/\s+/g, " ");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// global singleton
|
let langs: string[] = [];
|
||||||
export const i18n = new I18n();
|
|
||||||
|
|
||||||
import type { ModuleName } from "./i18n-modules";
|
export function toLocaleString(
|
||||||
|
date: Date,
|
||||||
|
options?: Intl.DateTimeFormatOptions
|
||||||
|
): string {
|
||||||
|
return date.toLocaleDateString(langs, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function localeCompare(
|
||||||
|
first: string,
|
||||||
|
second: string,
|
||||||
|
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";
|
||||||
|
|
Loading…
Reference in a new issue