feat: wire i18n services and shared UI

This commit is contained in:
poyrazavsever
2026-07-19 03:06:56 +03:00
parent 805beccf95
commit 826410f53e
11 changed files with 1330 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
import { cache } from "react";
import {
DEFAULT_LOCALE,
flattenCatalog,
getBuiltInCatalog,
type I18nNamespace,
} from "../../lib/i18n";
import { getSqliteConnection } from "../db/client";
import { instanceI18nSettings } from "../db/schema";
import { createI18nRepository } from "../repositories/i18n";
export type ResolvedCatalog = {
locale: string;
requestedLocale: string;
namespaces: I18nNamespace[];
catalogVersion: number;
fallbackChain: string[];
messages: Record<string, string>;
};
export const getResolvedCatalog = cache(
(
requestedLocale: string,
namespaces: readonly I18nNamespace[],
catalogVersion?: number,
): ResolvedCatalog => {
const { db } = getSqliteConnection();
const repository = createI18nRepository(db);
const version = catalogVersion ?? ensureCatalogVersion(db);
const fallbackChain = resolveFallbackChain(repository, requestedLocale);
const messages: Record<string, string> = {};
for (const locale of [...fallbackChain].reverse()) {
const builtIn = getBuiltInCatalog(locale);
if (builtIn) {
Object.assign(messages, flattenCatalog(builtIn, namespaces));
}
for (const row of repository.listUiTranslations(locale, namespaces)) {
messages[`${row.namespace}.${row.translationKey}`] = row.value;
}
}
return {
locale: fallbackChain[0] ?? DEFAULT_LOCALE,
requestedLocale,
namespaces: [...namespaces],
catalogVersion: version,
fallbackChain,
messages,
};
},
);
export function getCatalogVersion(): number {
return ensureCatalogVersion(getSqliteConnection().db);
}
function ensureCatalogVersion(db: ReturnType<typeof getSqliteConnection>["db"]): number {
const repository = createI18nRepository(db);
repository.createSettingsIfMissing();
return db.select({ catalogVersion: instanceI18nSettings.catalogVersion }).from(instanceI18nSettings).get()
?.catalogVersion ?? 1;
}
function resolveFallbackChain(
repository: ReturnType<typeof createI18nRepository>,
requestedLocale: string,
): string[] {
const locales = new Map(repository.listLocales().map((locale) => [locale.code, locale]));
const settings = repository.getSettings();
const start = locales.has(requestedLocale)
? requestedLocale
: settings?.defaultLocale ?? DEFAULT_LOCALE;
const chain: string[] = [];
const seen = new Set<string>();
let cursor: string | null = start;
while (cursor && !seen.has(cursor)) {
const locale = locales.get(cursor);
if (!locale || locale.status === "archived") break;
chain.push(cursor);
seen.add(cursor);
cursor = locale.fallbackLocale;
}
if (!chain.includes(DEFAULT_LOCALE)) {
chain.push(DEFAULT_LOCALE);
}
return chain;
}