wayBetter for X
This commit is contained in:
commit
df5dff619e
720 changed files with 147465 additions and 0 deletions
20
apps/desktop/src/i18n/catalogs.ts
Normal file
20
apps/desktop/src/i18n/catalogs.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Vite-only: every catalog becomes its own lazy chunk, loaded when that language is picked. The app's
|
||||
// messages (`locales/<tag>.json`) are merged over the framework's (`waybetter` kit words).
|
||||
import { setCatalogLoader } from './index.ts';
|
||||
|
||||
type CatalogModule = { messages?: Record<string, string> };
|
||||
|
||||
const app = import.meta.glob<CatalogModule>('../../locales/*.json', { import: 'default' });
|
||||
const framework = import.meta.glob<CatalogModule>('../../../../waybetter/packages/waybetter/locales/*.json', { import: 'default' });
|
||||
|
||||
const find = (table: Record<string, () => Promise<CatalogModule>>, tag: string) =>
|
||||
Object.entries(table).find(([path]) => path.endsWith(`/${tag}.json`))?.[1];
|
||||
|
||||
export function registerCatalogs() {
|
||||
setCatalogLoader(async tag => {
|
||||
if (tag === 'en') return null;
|
||||
const [own, kit] = await Promise.all([find(app, tag)?.(), find(framework, tag)?.()]);
|
||||
if (!own && !kit) return null;
|
||||
return { ...(kit?.messages ?? {}), ...(own?.messages ?? {}) };
|
||||
});
|
||||
}
|
||||
39
apps/desktop/src/i18n/index.ts
Normal file
39
apps/desktop/src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// The app's i18n instance for the shell, the wayBetter panel and the drafts popover (the X runtime
|
||||
// has its own, fed natively: src/inject/i18n.ts). The instance and its helpers are the framework's
|
||||
// (waybetter/packages/shell/src/frame/appI18n.ts): English (`locales/en.json`, plus the framework's
|
||||
// words) is always loaded; other catalogs load on demand (src/i18n/catalogs.ts registers the Vite
|
||||
// loader). Components call `useT()` so a language switch re-renders them in place.
|
||||
//
|
||||
// Contributors: see docs/TRANSLATING.md. Keys are typed from en.json: `t('common.close')`.
|
||||
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import source from '../../locales/en.json' with { type: 'json' };
|
||||
import kitSource from '../../../../waybetter/packages/waybetter/locales/en.json' with { type: 'json' };
|
||||
import languages from '../../locales/languages.json' with { type: 'json' };
|
||||
import type { Params } from '../../../../waybetter/packages/runtime/src/i18n.ts';
|
||||
import { createAppI18n, type AppLanguage } from '../../../../waybetter/packages/shell/src/frame/appI18n.ts';
|
||||
import { AREA_MESSAGES } from '../areas/messages.ts';
|
||||
import { setCoreText } from '../core/text.ts';
|
||||
|
||||
export type MessageKey = keyof typeof source.messages | keyof typeof kitSource.messages;
|
||||
export type { Params };
|
||||
export type Language = AppLanguage;
|
||||
|
||||
/** Every language with a catalog, English first (generated by `node scripts/i18n-check.mjs --write`). */
|
||||
export const LANGUAGES = languages as Language[];
|
||||
|
||||
/** Keys native code and X tabs need; the shell hands exactly these over (`i18n_set`). */
|
||||
export const NATIVE_PREFIXES = ['x.', 'kit.', 'plugin.', 'native.'] as const;
|
||||
|
||||
const dev = (() => { try { return !!(import.meta as { env?: { DEV?: boolean } }).env?.DEV; } catch { return false; } })();
|
||||
|
||||
export const { i18n, setCatalogLoader, t, tx, useT, useLocale, relativeTime, formatDate, formatNumber } = createAppI18n<MessageKey>({
|
||||
// The areas' new words (src/areas/<area>/messages.en.json) count as English until they are in en.json.
|
||||
source: { ...kitSource.messages, ...AREA_MESSAGES, ...source.messages },
|
||||
languages: LANGUAGES,
|
||||
useSyncExternalStore,
|
||||
dev,
|
||||
});
|
||||
|
||||
// Shared modules (src/core) speak through this instance in the shell.
|
||||
setCoreText((key, params) => i18n.tx(key, params));
|
||||
26
apps/desktop/src/i18n/language.ts
Normal file
26
apps/desktop/src/i18n/language.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Which language the app speaks: the framework's language state (waybetter
|
||||
// packages/shell/src/frame/language.ts) over the app's i18n instance. The owner picks "Follow X"
|
||||
// (default: the language X itself is set to, as X tabs report their `<html lang>`), "Follow system"
|
||||
// or a language; the shell switches every window live and hands native code and X tabs exactly the
|
||||
// strings they need (`i18n_set`). The drafts popover follows through localStorage.
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { createLanguage, resolveLocale as resolveLocaleCore, type LanguagePreference } from '../../../../waybetter/packages/shell/src/frame/language.ts';
|
||||
import { i18n, LANGUAGES, NATIVE_PREFIXES } from './index.ts';
|
||||
|
||||
export type { LanguagePreference };
|
||||
|
||||
/** The supported tier (languages.json `tier`): automatic choices resolve only to these. */
|
||||
export const SUPPORTED = LANGUAGES.filter(language => language.tier === 'supported').map(language => language.code);
|
||||
|
||||
export const language = createLanguage({ i18n, nativePrefixes: NATIVE_PREFIXES, native: { invoke, listen }, supported: SUPPORTED });
|
||||
|
||||
export const STORAGE_KEY = language.storageKey;
|
||||
export const initShellLanguage = language.initShell;
|
||||
export const initFollowerLanguage = language.initFollower;
|
||||
export const forcePreviewLanguage = language.forcePreview;
|
||||
|
||||
/** The catalog to use for a preference, X's reported language and the OS preferences. */
|
||||
export const resolveLocale = (preference: LanguagePreference, siteLang: string | null, system: readonly string[]) =>
|
||||
resolveLocaleCore(preference, siteLang, system, i18n.available, SUPPORTED);
|
||||
Loading…
Add table
Add a link
Reference in a new issue