Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ apps/web/public/vejledning/
apps/web/public/it/
# Built Arabic user guide.
apps/web/public/dalil/
# Built Simplified Chinese user guide.
apps/web/public/zh-cn/
# Built Traditional Chinese user guide.
apps/web/public/zh-tw/

# Built Turkish user guide.
apps/web/public/kilavuz/
Expand Down
2 changes: 1 addition & 1 deletion README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<a href="#layerling-mcp-skill"><img alt="Von einer KI steuerbar" src="https://img.shields.io/badge/KI--steuerbar-MCP--Server-16c0d4"></a>
</p>

<p><a href="README.md">English</a> · <strong>Deutsch</strong> · <a href="README.nl.md">Nederlands</a></p>
<p><a href="README.md">English</a> · <strong>Deutsch</strong> · <a href="README.nl.md">Nederlands</a> · <a href="README.zh-cn.md">简体中文</a> · <a href="README.zh-tw.md">繁體中文</a></p>
</div>

<p align="center">
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<a href="#layerling-mcp-skill"><img alt="Drivable by AI" src="https://img.shields.io/badge/AI--drivable-MCP%20server-16c0d4"></a>
</p>

<p><strong>English</strong> · <a href="README.de.md">Deutsch</a> · <a href="README.nl.md">Nederlands</a></p>
<p><strong>English</strong> · <a href="README.de.md">Deutsch</a> · <a href="README.nl.md">Nederlands</a> · <a href="README.zh-cn.md">简体中文</a> · <a href="README.zh-tw.md">繁體中文</a></p>
</div>

<p align="center">
Expand Down
2 changes: 1 addition & 1 deletion README.nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<a href="#layerling-mcp-skill"><img alt="Door AI te besturen" src="https://img.shields.io/badge/AI--drivable-MCP%20server-16c0d4"></a>
</p>

<p><a href="README.md">English</a> · <a href="README.de.md">Deutsch</a> · <strong>Nederlands</strong></p>
<p><a href="README.md">English</a> · <a href="README.de.md">Deutsch</a> · <strong>Nederlands</strong> · <a href="README.zh-cn.md">简体中文</a> · <a href="README.zh-tw.md">繁體中文</a></p>
</div>

<p align="center">
Expand Down
559 changes: 559 additions & 0 deletions README.zh-cn.md

Large diffs are not rendered by default.

554 changes: 554 additions & 0 deletions README.zh-tw.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion apps/web/src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MESSAGES_EN, type MessageKey } from "@/lib/messages.en";
import {
DEFAULT_LANGUAGE,
LANGUAGE_INFO,
LANGUAGE_TAG_ALIASES,
LANGUAGES,
isLanguage,
languageFromTag,
Expand Down Expand Up @@ -95,6 +96,8 @@ const LOADERS: Partial<Record<Language, CatalogueLoader>> = {
it: () => import("@/lib/messages.it").then((module) => module.MESSAGES_IT),
ar: () => import("@/lib/messages.ar").then((module) => module.MESSAGES_AR),
ja: () => import("@/lib/messages.ja").then((module) => module.MESSAGES_JA),
"zh-cn": () => import("@/lib/messages.zh-cn").then((module) => module.MESSAGES_ZH_CN),
"zh-tw": () => import("@/lib/messages.zh-tw").then((module) => module.MESSAGES_ZH_TW),
};

const catalogues = createCatalogueStore({ en: MESSAGES_EN, de: MESSAGES_DE }, LOADERS);
Expand Down Expand Up @@ -189,7 +192,7 @@ export function detectLanguage(): Language {
*/
export const LANGUAGE_BOOT_SCRIPT = `try{var m=${JSON.stringify(
Object.fromEntries(LANGUAGES.map((language) => [language, LANGUAGE_INFO[language].htmlLang])),
)},l=null;try{var s=localStorage.getItem(${JSON.stringify(LANGUAGE_STORAGE_KEY)});if(m[s])l=s}catch(e){}if(!l){var t=navigator.languages||[navigator.language];for(var i=0;i<t.length&&!l;i++){var p=String(t[i]||"").trim().toLowerCase().split("-"),r=p.slice(0,2).join("-");l=m[r]?r:m[p[0]]?p[0]:null}}document.documentElement.lang=m[l||${JSON.stringify(DEFAULT_LANGUAGE)}]}catch(e){}`;
)},a=${JSON.stringify(LANGUAGE_TAG_ALIASES)},l=null;try{var s=localStorage.getItem(${JSON.stringify(LANGUAGE_STORAGE_KEY)});if(m[s])l=s}catch(e){}if(!l){var t=navigator.languages||[navigator.language];for(var i=0;i<t.length&&!l;i++){var p=String(t[i]||"").trim().toLowerCase().split("-"),r=p.slice(0,2).join("-");l=m[r]?r:a[r]?a[r]:m[p[0]]?p[0]:null}}document.documentElement.lang=m[l||${JSON.stringify(DEFAULT_LANGUAGE)}]}catch(e){}`;

export function translate(
language: Language,
Expand Down
96 changes: 95 additions & 1 deletion apps/web/src/lib/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@
* (Čeština, Dansk, Deutsch, Français, ...); then the others, by the name in their own script.
* A new language goes in at its place, not at the end.
*/
export const LANGUAGES = ["en", "cs", "da", "de", "es", "fr", "it", "hu", "nl", "pl", "pt-br", "sl", "tr", "ru", "ar", "ja"] as const;
export const LANGUAGES = ["en", "cs", "da", "de", "es", "fr", "it", "hu", "nl", "pl", "pt-br", "sl", "tr", "ru", "ar", "ja", "zh-cn", "zh-tw"] as const;
export type Language = (typeof LANGUAGES)[number];
export const DEFAULT_LANGUAGE: Language = "en";

/**
* Tags that name a script or a region before a language entry of their own: browsers write
* zh-Hans, zh-Hant, zh-HK and zh-MO instead of zh-CN and zh-TW.
*/
export const LANGUAGE_TAG_ALIASES: Readonly<Record<string, Language>> = {
"zh-hans": "zh-cn",
"zh-hant": "zh-tw",
"zh-hk": "zh-tw",
"zh-mo": "zh-tw",
};

/** The words of a guide page that are not chapter text, in the language of the page. */
export type GuideStrings = {
site: string;
Expand Down Expand Up @@ -742,6 +753,87 @@ export const LANGUAGE_INFO: Readonly<Record<Language, LanguageInfo>> = {
printButton: "印刷",
},
},
// Simplified Chinese: the whole interface, the 17 chapters of the guide and a README of its own.
// The code carries the region, as Brazilian Portuguese does, so a browser that asks for zh-CN or
// zh-Hans-CN is served it; other Chinese variants stay on English until a translation exists.
"zh-cn": {
name: "简体中文",
htmlLang: "zh-CN",
numberLocale: "zh-CN",
quotes: ["“", "”"],
guideDir: "zh-cn",
communityUrl: GITHUB_DISCUSSIONS,
readme: "README.zh-cn.md",
quickstartAnchor: "windows-快速上手",
legal: { imprint: "版本说明", privacy: "隐私政策" },
// Chinese keyboards label the keys in English; only the mouse actions get Chinese names.
keyNames: { Ctrl: "Ctrl", Shift: "Shift", Delete: "Delete", Backspace: "Backspace", Home: "Home", End: "End", "Page Up": "Page Up", "Page Down": "Page Down", Enter: "Enter", Click: "单击", RightClick: "右键单击", Drag: "拖动" },
asciiUmlauts: false,
complete: true,
guide: {
site: "layerling 用户指南",
home: "用户指南",
openEditor: "打开编辑器",
backToEditor: "返回编辑器",
editorElsewhere: "编辑器已在另一个标签页中打开,请切换到那里。",
chapters: "章节",
onThisPage: "本页内容",
previous: "上一章",
next: "下一章",
overviewLead: "layerling 的全部功能,逐步讲解。图片取自运行中的程序,并随每个版本更新。",
sourceNote: "本指南随程序一同完善。如有缺漏或错误,请到",
forum: "讨论区反馈",
notTranslated: "本章尚未翻译为简体中文,以下为英文原文。",
languageMenu: "语言",
search: "搜索指南",
searchNothing: "未找到结果。",
printAll: "打印完整指南",
printAllTitle: "完整用户指南",
printAllLead: "所有章节依次排列,可打印或保存为 PDF:按 Ctrl+P,并在打印设置中关闭“页眉和页脚”。只打印部分章节时,请在其中选择页码。",
printContents: "目录",
printButton: "打印",
},
},
// Traditional Chinese as written in Taiwan: the whole interface, the 17 chapters of the guide
// and a README of its own. zh-TW, zh-Hant and the Hong Kong and Macau tags are matched to it.
"zh-tw": {
name: "繁體中文",
htmlLang: "zh-TW",
numberLocale: "zh-TW",
quotes: ["「", "」"],
guideDir: "zh-tw",
communityUrl: GITHUB_DISCUSSIONS,
readme: "README.zh-tw.md",
quickstartAnchor: "windows-快速上手",
legal: { imprint: "版本說明", privacy: "隱私權政策" },
// Chinese keyboards label the keys in English; only the mouse actions get Chinese names.
keyNames: { Ctrl: "Ctrl", Shift: "Shift", Delete: "Delete", Backspace: "Backspace", Home: "Home", End: "End", "Page Up": "Page Up", "Page Down": "Page Down", Enter: "Enter", Click: "點擊", RightClick: "右鍵點擊", Drag: "拖曳" },
asciiUmlauts: false,
complete: true,
guide: {
site: "layerling 使用者指南",
home: "使用者指南",
openEditor: "開啟編輯器",
backToEditor: "返回編輯器",
editorElsewhere: "編輯器已在另一個分頁中開啟,請切換到那裡。",
chapters: "章節",
onThisPage: "本頁內容",
previous: "上一章",
next: "下一章",
overviewLead: "layerling 的全部功能,逐步說明。圖片取自執行中的程式,並隨每個版本更新。",
sourceNote: "本指南隨程式一同完善。如有缺漏或錯誤,請到",
forum: "討論區告訴我們",
notTranslated: "本章尚未翻譯為繁體中文,以下為英文原文。",
languageMenu: "語言",
search: "搜尋指南",
searchNothing: "找不到結果。",
printAll: "列印完整指南",
printAllTitle: "完整使用者指南",
printAllLead: "所有章節依次排列,可列印或儲存為 PDF:按 Ctrl+P,並在列印設定中關閉「頁首及頁尾」。只列印部分章節時,請在其中選擇頁碼。",
printContents: "目錄",
printButton: "列印",
},
},
};

export function isLanguage(value: unknown): value is Language {
Expand All @@ -757,6 +849,8 @@ export function languageFromTag(tag: string | undefined | null): Language | null
const subtags = tag?.trim().toLowerCase().split("-") ?? [];
const withRegion = subtags.slice(0, 2).join("-");
if (isLanguage(withRegion)) return withRegion;
const alias = LANGUAGE_TAG_ALIASES[withRegion];
if (alias) return alias;
const primary = subtags[0];
return isLanguage(primary) ? primary : null;
}
Loading