feat(frontend): russian and english interface with a language choice in settings, soon stubs removed
This commit is contained in:
1 parent
3d2f670e87
commit
3460246749
13 files changed
+313
-132
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import { type MessageKey, messages } from "#lib/i18n/messages.js";
|
||||
import type { Locale, Text } from "#lib/i18n/types.js";
|
||||
import { browser } from "$app/env";
|
||||
|
||||
type Preference = Locale | "system";
|
||||
type Params = Record<string, number | string>;
|
||||
|
||||
const STORAGE_KEY = "bg.locale";
|
||||
const PARAM = /\{(\w+)\}/g;
|
||||
const TAGS: Record<Locale, string> = { en: "en-GB", ru: "ru-RU" };
|
||||
const plurals: Record<Locale, Intl.PluralRules> = {
|
||||
en: new Intl.PluralRules(TAGS.en),
|
||||
ru: new Intl.PluralRules(TAGS.ru),
|
||||
};
|
||||
|
||||
function readPreference(): Preference {
|
||||
if (!browser) {
|
||||
return "system";
|
||||
}
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
return stored === "en" || stored === "ru" ? stored : "system";
|
||||
}
|
||||
|
||||
function systemLocale(): Locale {
|
||||
if (!browser) {
|
||||
return "ru";
|
||||
}
|
||||
for (const language of navigator.languages) {
|
||||
const base = language.toLowerCase().slice(0, 2);
|
||||
if (base === "ru" || base === "en") {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
return "en";
|
||||
}
|
||||
|
||||
function createLocale() {
|
||||
let preference = $state<Preference>(readPreference());
|
||||
const current = $derived<Locale>(
|
||||
preference === "system" ? systemLocale() : preference
|
||||
);
|
||||
|
||||
return {
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
get preference() {
|
||||
return preference;
|
||||
},
|
||||
set(value: Preference) {
|
||||
preference = value;
|
||||
if (browser) {
|
||||
localStorage.setItem(STORAGE_KEY, value);
|
||||
}
|
||||
},
|
||||
get tag() {
|
||||
return TAGS[current];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const locale = createLocale();
|
||||
|
||||
function pick(text: Text, count: number | undefined): string {
|
||||
if (typeof text === "string") {
|
||||
return text;
|
||||
}
|
||||
return text[plurals[locale.current].select(count ?? 0)] ?? text.other;
|
||||
}
|
||||
|
||||
export function t(key: MessageKey, params?: Params): string {
|
||||
const count = typeof params?.count === "number" ? params.count : undefined;
|
||||
const text = pick(messages[key][locale.current], count);
|
||||
if (!params) {
|
||||
return text;
|
||||
}
|
||||
return text.replace(PARAM, (match, name: string) =>
|
||||
name in params ? String(params[name]) : match
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { common } from "#lib/i18n/messages/common.js";
|
||||
import { settings } from "#lib/i18n/messages/settings.js";
|
||||
|
||||
export const messages = {
|
||||
...common,
|
||||
...settings,
|
||||
};
|
||||
|
||||
export type MessageKey = keyof typeof messages;
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Messages } from "#lib/i18n/types.js";
|
||||
|
||||
export const common = {
|
||||
"common.back": { en: "Back", ru: "Назад" },
|
||||
"common.cancel": { en: "Cancel", ru: "Отмена" },
|
||||
"common.close": { en: "Close", ru: "Закрыть" },
|
||||
"common.delete": { en: "Delete", ru: "Удалить" },
|
||||
"common.save": { en: "Save", ru: "Сохранить" },
|
||||
} satisfies Messages;
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Messages } from "#lib/i18n/types.js";
|
||||
|
||||
export const settings = {
|
||||
"settings.about.license": { en: "GPL-3.0 license", ru: "Лицензия GPL-3.0" },
|
||||
"settings.about.port": {
|
||||
en: "UI ported from Telegram A",
|
||||
ru: "UI портирован из Telegram A",
|
||||
},
|
||||
"settings.about.version": { en: "Version {version}", ru: "Версия {version}" },
|
||||
"settings.alerts": { en: "Alerts", ru: "Оповещения" },
|
||||
"settings.appearance": { en: "Appearance", ru: "Внешний вид" },
|
||||
"settings.bots": { en: "Bots", ru: "Боты" },
|
||||
"settings.jobs": { en: "Backfill and jobs", ru: "Бэкфилл и задачи" },
|
||||
"settings.language": { en: "Language", ru: "Язык" },
|
||||
"settings.language.system": { en: "System", ru: "Как в системе" },
|
||||
"settings.policy": { en: "Capture policy", ru: "Политика захвата" },
|
||||
"settings.shares": { en: "Files by link", ru: "Файлы по ссылке" },
|
||||
"settings.storage": { en: "Storage", ru: "Хранилище" },
|
||||
"settings.stories": { en: "Stories", ru: "Сторис" },
|
||||
"settings.theme.dark": { en: "Dark", ru: "Тёмная" },
|
||||
"settings.theme.light": { en: "Light", ru: "Светлая" },
|
||||
"settings.theme.system": { en: "System", ru: "Системная" },
|
||||
"settings.title": { en: "Settings", ru: "Настройки" },
|
||||
"settings.watches": { en: "Watches", ru: "Наблюдения" },
|
||||
} satisfies Messages;
|
||||
@@ -0,0 +1,9 @@
|
||||
export type Locale = "en" | "ru";
|
||||
|
||||
export type Plural = Partial<Record<Intl.LDMLPluralRule, string>> & {
|
||||
other: string;
|
||||
};
|
||||
|
||||
export type Text = string | Plural;
|
||||
|
||||
export type Messages = Record<string, Record<Locale, Text>>;
|
||||
Reference in new issue
Block a user