anki/ts/lib/i18n.ts
Damien Elmes aea0a6fcc6 initial Bazel conversion
Running and testing should be working on the three platforms, but
there's still a fair bit that needs to be done:

- Wheel building + testing in a venv still needs to be implemented.
- Python requirements still need to be compiled with piptool and pinned;
need to compile on all platforms then merge
- Cargo deps in cargo/ and rslib/ need to be cleaned up, and ideally
unified into one place
- Currently using rustls to work around openssl compilation issues
on Linux, but this will break corporate proxies with custom SSL
authorities; need to conditionally use openssl or use
https://github.com/seanmonstar/reqwest/pull/1058
- Makefiles and docs still need cleaning up
- It may make sense to reparent ts/* to the top level, as we don't
nest the other modules under a specific language.
- rspy and pylib must always be updated in lock-step, so merging
rspy into pylib as a private module would simplify things.
- Merging desktop-ftl and mobile-ftl into the core ftl would make
managing and updating translations easier.
- Obsolete scripts need removing.
- And probably more.
2020-11-01 14:26:58 +10:00

86 lines
2.4 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import pb from "./fluent_proto";
import "intl-pluralrules";
import { FluentBundle, FluentResource, FluentNumber } from "@fluent/bundle/compat";
type RecordVal = number | string | FluentNumber;
function formatNumbers(args?: Record<string, RecordVal>): void {
if (!args) {
return;
}
for (const key of Object.keys(args)) {
if (typeof args[key] === "number") {
args[key] = new FluentNumber(args[key] as number, {
maximumFractionDigits: 2,
});
}
}
}
export class I18n {
bundles: FluentBundle[] = [];
langs: string[] = [];
TR = pb.FluentProto.FluentString;
tr(id: pb.FluentProto.FluentString, args?: Record<string, RecordVal>): string {
formatNumbers(args);
const key = this.keyName(id);
for (const bundle of this.bundles) {
const msg = bundle.getMessage(key);
if (msg && msg.value) {
return bundle.formatPattern(msg.value, args);
}
}
return `missing key: ${key}`;
}
supportsVerticalText(): boolean {
const firstLang = this.bundles[0].locales[0];
return (
firstLang.startsWith("ja") ||
firstLang.startsWith("zh") ||
firstLang.startsWith("ko")
);
}
direction(): string {
const firstLang = this.bundles[0].locales[0];
if (
firstLang.startsWith("ar") ||
firstLang.startsWith("he") ||
firstLang.startsWith("fa")
) {
return "rtl";
} else {
return "ltr";
}
}
private keyName(msg: pb.FluentProto.FluentString): string {
return this.TR[msg].toLowerCase().replace(/_/g, "-");
}
}
export async function setupI18n(): Promise<I18n> {
const i18n = new I18n();
const resp = await fetch("/_anki/i18nResources", { method: "POST" });
if (!resp.ok) {
throw Error(`unexpected reply: ${resp.statusText}`);
}
const json = await resp.json();
for (const resourceText of json.resources) {
const bundle = new FluentBundle(json.langs);
const resource = new FluentResource(resourceText);
bundle.addResource(resource);
i18n.bundles.push(bundle);
}
i18n.langs = json.langs;
return i18n;
}