config: optional defaults.lang to pin a default UI language

When set (and the user hasn't explicitly chosen one), it overrides
browser detection after the runtime config loads. Omitted = auto-detect
as before.
This commit is contained in:
reverse 2026-06-20 16:28:25 +00:00
parent ed2744ef8c
commit 5d5b54cfc4
No known key found for this signature in database
4 changed files with 17 additions and 2 deletions

View file

@ -1,7 +1,7 @@
// Tchatou service worker — installable PWA + offline app shell.
// Scope: /app/. Only handles same-origin /app/ GETs; the IRC websocket and all
// API calls (cloudflare, change_password, upload) pass straight through.
const CACHE = 'tchatou-v50';
const CACHE = 'tchatou-v51';
const SHELL = ['/app/', '/app/index.html', '/app/favicon.svg', '/app/orbit-icon.svg', '/app/manifest.webmanifest'];
self.addEventListener('install', (e) => {

View file

@ -39,6 +39,7 @@ export interface AppConfig {
sound: boolean; // blip on mention / PM
hideJoinQuit: boolean; // hide join/part/quit noise
clock24: boolean; // 24h timestamps
lang?: string; // force a default UI language (e.g. 'en'); omit to auto-detect from the browser
};
/** Feature switches — turn whole features off for a deployment. */
features: {

View file

@ -65,6 +65,18 @@ void i18n.use(initReactI18next).init({
document.documentElement.lang = i18n.language;
// Apply a deployment's default UI language (config.defaults.lang) — but only for
// users who haven't explicitly picked one (setLang writes KEY). Called after the
// runtime config loads, so it can override the browser-detected language.
export function applyConfigDefaultLang(code?: string): void {
if (!code) return;
if (localStorage.getItem(KEY)) return; // explicit user choice — honour it
if (!CODES.includes(code)) return; // unknown language — ignore
if (i18n.language === code) return;
void i18n.changeLanguage(code);
document.documentElement.lang = code;
}
export function getLang(): string {
return i18n.language;
}

View file

@ -2,9 +2,10 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { applyTheme, getTheme } from './ui/theme.ts'
import './i18n'
import { applyConfigDefaultLang } from './i18n'
import './index.css'
import { initViewport } from './ui/viewport.ts'
import { loadConfig } from './config.ts'
import { loadConfig, getConfig } from './config.ts'
// Track the visual viewport so the layout shrinks above the on-screen keyboard.
initViewport()
@ -14,6 +15,7 @@ initViewport()
// a rebuild.
loadConfig().then(async () => {
applyTheme(getTheme()) // re-apply now that config (default theme) is loaded
applyConfigDefaultLang(getConfig().defaults.lang) // honour a config-pinned default language
const { default: App } = await import('./App.tsx') // also creates the store
// Plugin subsystem: publish window.Orbit, bridge app/IRC events onto the bus,
// then load operator-listed plugins from config. After the store exists,