Anki/ts/lib/tslib/i18n/utils.ts
Damien Elmes 9f55cf26fc
Switch to SvelteKit (#3077)
* Update to latest Node LTS

* Add sveltekit

* Split tslib into separate @generated and @tslib components

SvelteKit's path aliases don't support multiple locations, so our old
approach of using @tslib to refer to both ts/lib and out/ts/lib will no
longer work. Instead, all generated sources and their includes are
placed in a separate out/ts/generated folder, and imported via @generated
instead. This also allows us to generate .ts files, instead of needing
to output separate .d.ts and .js files.

* Switch package.json to module type

* Avoid usage of baseUrl

Incompatible with SvelteKit

* Move sass into ts; use relative links

SvelteKit's default sass support doesn't allow overriding loadPaths

* jest->vitest, graphs example working with yarn dev

* most pages working in dev mode

* Some fixes after rebasing

* Fix/silence some svelte-check errors

* Get image-occlusion working with Fabric types

* Post-rebase lock changes

* Editor is now checked

* SvelteKit build integrated into ninja

* Use the new SvelteKit entrypoint for pages like congrats/deck options/etc

* Run eslint once for ts/**; fix some tests

* Fix a bunch of issues introduced when rebasing over latest main

* Run eslint fix

* Fix remaining eslint+pylint issues; tests now all pass

* Fix some issues with a clean build

* Latest bufbuild no longer requires @__PURE__ hack

* Add a few missed dependencies

* Add yarn.bat to fix Windows build

* Fix pages failing to show when ANKI_API_PORT not defined

* Fix svelte-check and vitest on Windows

* Set node path in ./yarn

* Move svelte-kit output to ts/.svelte-kit

Sadly, I couldn't figure out a way to store it in out/ if out/ is
a symlink, as it breaks module resolution when SvelteKit is run.

* Allow HMR inside Anki

* Skip SvelteKit build when HMR is defined

* Fix some post-rebase issues

I should have done a normal merge instead.
2024-03-31 09:16:31 +01:00

105 lines
2.9 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import "intl-pluralrules";
import { i18nResources } from "@generated/backend";
import type { ModuleName } from "@generated/ftl";
import { FluentBundle, FluentResource } from "@generated/ftl";
import { firstLanguage, setBundles } from "@generated/ftl";
export function supportsVerticalText(): boolean {
const firstLang = firstLanguage();
return (
firstLang.startsWith("ja")
|| firstLang.startsWith("zh")
|| firstLang.startsWith("ko")
);
}
export function direction(): string {
const firstLang = firstLanguage();
if (
firstLang.startsWith("ar")
|| firstLang.startsWith("he")
|| firstLang.startsWith("fa")
) {
return "rtl";
} else {
return "ltr";
}
}
export function weekdayLabel(n: number): string {
const firstLang = firstLanguage();
const now = new Date();
const daysFromToday = -now.getDay() + n;
const desiredDay = new Date(now.getTime() + daysFromToday * 86_400_000);
return desiredDay.toLocaleDateString(firstLang, {
weekday: "narrow",
});
}
let langs: string[] = [];
export function localizedDate(
date: Date,
options?: Intl.DateTimeFormatOptions,
): string {
return date.toLocaleDateString(langs, options);
}
export function localizedNumber(n: number, precision = 2): string {
const round = Math.pow(10, precision);
const rounded = Math.round(n * round) / round;
return rounded.toLocaleString(langs);
}
export function localeCompare(
first: string,
second: string,
options?: Intl.CollatorOptions,
): number {
return first.localeCompare(second, langs, options);
}
/** Treat text like HTML, merging multiple spaces and converting
newlines to spaces. */
export function withCollapsedWhitespace(s: string): string {
return s.replace(/\s+/g, " ");
}
export function withoutUnicodeIsolation(s: string): string {
return s.replace(/[\u2068-\u2069]+/g, "");
}
export async function setupI18n(args: { modules: ModuleName[] }): Promise<void> {
const resources = await i18nResources(args);
const json = JSON.parse(new TextDecoder().decode(resources.json));
const newBundles: FluentBundle[] = [];
for (const res in json.resources) {
const text = json.resources[res];
const lang = json.langs[res];
const bundle = new FluentBundle([lang, "en-US"]);
const resource = new FluentResource(text);
bundle.addResource(resource);
newBundles.push(bundle);
}
setBundles(newBundles);
langs = json.langs;
document.dir = direction();
}
let globalI18n: Promise<void> | undefined;
export async function setupGlobalI18n(): Promise<void> {
if (!globalI18n) {
globalI18n = setupI18n({
modules: [],
});
}
await globalI18n;
}