feat: add Russian localization and independent STT languages
CI / Typecheck, tests, lint (push) Canceled after 0s
CI / Standalone (mac-arm64) (push) Canceled after 0s
CI / Standalone (linux-x64) (push) Canceled after 0s
CI / Standalone (windows-x64) (push) Canceled after 0s

This commit is contained in:
2026-10-25 17:18:44 +03:00
parent b277ee3e0f
commit a50ca4b867
93 changed files with 3261 additions and 904 deletions
+22 -9
View File
@@ -1,5 +1,9 @@
import { headers } from "next/headers";
import { readSettingsRecord } from "@/src/server";
import { resolveLanguage, systemLanguage, translate } from "@/src/core";
import { I18nProvider } from "@/components/i18n";
import type { Metadata } from "next";
import { JetBrains_Mono, Source_Sans_3 } from "next/font/google";
import { JetBrains_Mono, Source_Sans_3, Noto_Sans } from "next/font/google";
import "katex/dist/katex.min.css";
import "./globals.css";
import { AppShell, SidebarProvider } from "@/components/sidebar";
@@ -10,21 +14,29 @@ import UpdateBanner from "@/components/update-banner";
// Chrome = JetBrains Mono, teaching prose = Source Sans 3, titles = CMU
// Serif (the bundled lh-cmu @font-face in globals.css — no CDN).
const ui = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains" });
const ui = JetBrains_Mono({ subsets: ["latin", "cyrillic"], variable: "--font-jetbrains" });
const prose = Source_Sans_3({ subsets: ["latin"], variable: "--font-sans3" });
export const metadata: Metadata = {
title: "Learning Harness",
description: "A teacher agent with a memory — sessions, quizzes, and a knowledge graph of what you know.",
};
const russianProse = Noto_Sans({ subsets: ["latin", "cyrillic"], variable: "--font-russian-prose" });
export default function RootLayout({ children }: { children: React.ReactNode }) {
export async function generateMetadata(): Promise<Metadata> {
const language = resolveLanguage(readSettingsRecord()?.general?.language, (await headers()).get("accept-language"));
return {
title: "Learning Harness",
description: translate(language, "A teacher agent with a memory — sessions, quizzes, and a knowledge graph of what you know."),
};
}
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const general = readSettingsRecord()?.general ?? {};
const system = systemLanguage((await headers()).get("accept-language"));
const language = resolveLanguage(general.language, system);
return (
<html lang="en" className={`dark ${ui.variable} ${prose.variable}`}>
<html lang={language} className={`dark ${ui.variable} ${prose.variable} ${russianProse.variable}`}>
<body className="min-h-dvh antialiased">
{/* The notification channel rides OUTSIDE everything: sidebar, dock,
and pages all subscribe as the polls retire (push S3, 2026-10-02). */}
<ServerEventsProvider>
<I18nProvider initialGeneral={general} system={system}><ServerEventsProvider>
<AppearanceProvider>
<SidebarProvider>
<ActivityProvider>
@@ -35,6 +47,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</ServerEventsProvider>
{/* Auto-update banner (Wave C): fixed card, Electron-only pushes. */}
<UpdateBanner />
</I18nProvider>
</body>
</html>
);