Skip to content

Commit b91338e

Browse files
committed
i18n: refresh shared UI translations
1 parent 7a02c45 commit b91338e

38 files changed

Lines changed: 23517 additions & 10817 deletions

_tools/check-ui-zh.mjs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { readFileSync } from "node:fs";
2+
import { resolve } from "node:path";
3+
4+
const root = resolve(import.meta.dirname, "..");
5+
const messageDir = resolve(root, "src/common/messagesJson");
6+
const en = JSON.parse(readFileSync(resolve(messageDir, "en.json"), "utf8"));
7+
const zh = JSON.parse(readFileSync(resolve(messageDir, "zh.json"), "utf8"));
8+
const zhTw = JSON.parse(readFileSync(resolve(messageDir, "zh-tw.json"), "utf8"));
9+
const placeholderRe = /(%\{[^}]+\}|\$\{[^}]+\})/g;
10+
const tokens = (value) => Array.from(String(value).matchAll(placeholderRe), (match) => match[1]).sort();
11+
12+
const allowedFallbackKey = /^(lang-.*|K\.long_p2p_sync|K\.short_p2p_sync|moduleLiveSyncMain\.logPluginVersion|obsidianLiveSyncSettingTab\.linkTroubleshooting|obsidianLiveSyncSettingTab\.(optionCouchDB|optionLiveSync|titleCouchDB)|P2P\.PaneTitle|TweakMismatchResolve\.Table\.Row|Ui\.SetupWizard\.SetupRemote\.CouchDbOption|Ui\.UseSetupURI\.Label|moduleMigration\.docUri|Setup\.QRCode)$/;
13+
const allowedFallbackValue = /^(MB|CouchDB|P2P|S3|MinIO|R2|JWT|IndexedDB|IDB|E2EE|Hatch|Vault|Obsidian|LiveSync|Self-hosted LiveSync|PouchDB|WebRTC|WebSocket|HTTP|HTTPS|Red Flag)$/;
14+
15+
function checkLanguage(lang, messages) {
16+
const issues = [];
17+
for (const [key, enValue] of Object.entries(en)) {
18+
if (!(key in messages)) {
19+
issues.push(`${lang}: missing key ${key}`);
20+
continue;
21+
}
22+
const value = String(messages[key]);
23+
if (/[?]{2,}|/.test(value)) {
24+
issues.push(`${lang}: damaged value ${key}=${JSON.stringify(value)}`);
25+
}
26+
if (
27+
value === enValue &&
28+
!allowedFallbackKey.test(key) &&
29+
!allowedFallbackValue.test(String(enValue))
30+
) {
31+
issues.push(`${lang}: untranslated value ${key}`);
32+
}
33+
const expectedTokens = JSON.stringify(tokens(enValue));
34+
const actualTokens = JSON.stringify(tokens(value));
35+
if (expectedTokens !== actualTokens) {
36+
issues.push(`${lang}: placeholder mismatch ${key}: expected ${expectedTokens}, got ${actualTokens}`);
37+
}
38+
}
39+
return issues;
40+
}
41+
42+
const issues = [...checkLanguage("zh", zh), ...checkLanguage("zh-tw", zhTw)];
43+
if (issues.length > 0) {
44+
console.error(issues.slice(0, 50).join("\n"));
45+
if (issues.length > 50) {
46+
console.error(`...and ${issues.length - 50} more issues`);
47+
}
48+
process.exit(1);
49+
}
50+
51+
console.log("Chinese UI i18n check passed");

src/UI/components/Check.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script lang="ts">
2+
import { translateIfAvailable } from "@lib/common/i18n";
3+
24
type Props = {
35
title: string;
46
value: boolean;
@@ -8,11 +10,12 @@
810
};
911
1012
let { title, value = $bindable(), noteOnSelected, noteOnUnselected, children }: Props = $props();
13+
const displayTitle = $derived.by(() => translateIfAvailable(title));
1114
</script>
1215

1316
<label class="choice-row">
1417
<input type="checkbox" bind:checked={value} />
15-
<span class="choice-title">{title}</span>
18+
<span class="choice-title">{displayTitle}</span>
1619
</label>
1720
<div class="choice-notes">
1821
<!-- TODO Highlight selected option -->

src/UI/components/Decision.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { fireAndForget } from "octagonal-wheels/promises";
3+
import { translateIfAvailable } from "@lib/common/i18n";
34
45
type Props = {
56
title: string;
@@ -10,6 +11,7 @@
1011
disabled?: boolean;
1112
};
1213
let { title, commit, additionalClasses, important, disabled = $bindable(), destructive }: Props = $props();
14+
const displayTitle = $derived.by(() => translateIfAvailable(title));
1315
function onclick() {
1416
fireAndForget(async () => commit());
1517
}
@@ -18,5 +20,5 @@
1820
<button
1921
class="button {additionalClasses} {important ? 'mod-cta' : ''} {destructive ? 'mod-destructive' : ''}"
2022
{onclick}
21-
{disabled}>{title}</button
23+
{disabled}>{displayTitle}</button
2224
>

src/UI/components/DialogHeader.svelte

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { onMount, tick } from "svelte";
3+
import { translateIfAvailable } from "@lib/common/i18n";
34
import { getDialogContext } from "../svelteDialog.ts";
45
56
type Props = {
@@ -8,24 +9,26 @@
89
children?: () => unknown;
910
};
1011
let { title = $bindable(), subtitle, children }: Props = $props();
12+
const displayTitle = $derived.by(() => translateIfAvailable(title));
13+
const displaySubtitle = $derived.by(() => (subtitle ? translateIfAvailable(subtitle) : ""));
1114
1215
$effect(() => {
13-
if (title) {
14-
context.setTitle(`${title}${subtitle ? ` - ${subtitle}` : ""}`);
16+
if (displayTitle) {
17+
context.setTitle(`${displayTitle}${displaySubtitle ? ` - ${displaySubtitle}` : ""}`);
1518
}
1619
});
1720
const context = getDialogContext();
1821
onMount(async () => {
19-
context.setTitle(`${title}${subtitle ? ` - ${subtitle}` : ""}`);
22+
context.setTitle(`${displayTitle}${displaySubtitle ? ` - ${displaySubtitle}` : ""}`);
2023
await tick();
2124
document.querySelector(".modal")?.scrollTo(0, 0);
2225
});
2326
</script>
2427

2528
<div class="dialog-header">
26-
<h2>{title}</h2>
27-
{#if subtitle}
28-
<h4>{subtitle}</h4>
29+
<h2>{displayTitle}</h2>
30+
{#if displaySubtitle}
31+
<h4>{displaySubtitle}</h4>
2932
{/if}
3033
</div>
3134

src/UI/components/ExtraItems.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
<script lang="ts">
2+
import { translateIfAvailable } from "@lib/common/i18n";
3+
24
type Props = {
35
title?: string;
46
children?: () => any;
57
};
68
const { children, title }: Props = $props();
9+
const displayTitle = $derived.by(() => (title ? translateIfAvailable(title) : ""));
710
</script>
811

912
<details>
10-
<summary>{title}</summary>
13+
<summary>{displayTitle}</summary>
1114
<div class="sub-section">
1215
{@render children?.()}
1316
</div>

src/UI/components/Guidance.svelte

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { $t as t } from "@lib/common/i18n";
2+
import { translateIfAvailable } from "@lib/common/i18n";
33
44
type Props = {
55
children?: () => any;
@@ -11,12 +11,13 @@
1111
const cssClass = $derived.by(() => {
1212
return important ? "guidance important" : "guidance";
1313
});
14-
const translatedMessage = $derived.by(() => (message ? t(message) : ""));
14+
const translatedTitle = $derived.by(() => (title ? translateIfAvailable(title) : ""));
15+
const translatedMessage = $derived.by(() => (message ? translateIfAvailable(message) : ""));
1516
</script>
1617

1718
<div class={cssClass}>
18-
{#if title}
19-
<h3>{title}</h3>
19+
{#if translatedTitle}
20+
<h3>{translatedTitle}</h3>
2021
{/if}
2122
{#if translatedMessage}
2223
<p>{translatedMessage}</p>

src/UI/components/InfoNote.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script lang="ts">
2+
import { translateIfAvailable } from "@lib/common/i18n";
23
type Props = {
34
title?: string;
5+
message?: string;
46
children?: () => any;
57
cssClass?: string;
68
warning?: boolean;
@@ -10,6 +12,7 @@
1012
};
1113
const {
1214
title,
15+
message,
1316
children,
1417
cssClass,
1518
warning: isWarning,
@@ -28,11 +31,14 @@
2831
return "";
2932
}
3033
});
34+
const displayTitle = $derived.by(() => (title ? translateIfAvailable(title) : ""));
35+
const displayMessage = $derived.by(() => (message ? translateIfAvailable(message) : ""));
3136
</script>
3237

3338
{#if visible === undefined || visible === true}
3439
<div class={(cssClass ?? "") + " " + derivedCssClass}>
35-
{#if title}<h3>{title}</h3>{/if}
40+
{#if displayTitle}<h3>{displayTitle}</h3>{/if}
41+
{#if displayMessage}<p>{displayMessage}</p>{/if}
3642
{@render children?.()}
3743
</div>
3844
{/if}

src/UI/components/InputRow.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<script lang="ts">
2+
import { translateIfAvailable } from "@lib/common/i18n";
3+
24
type Props = {
35
label: string;
46
children?: () => any;
57
};
68
const { label, children }: Props = $props();
9+
const displayLabel = $derived.by(() => translateIfAvailable(label));
710
</script>
811

912
<label class="row"
10-
><span>{label}</span>
13+
><span>{displayLabel}</span>
1114
{@render children?.()}
12-
</label>
15+
</label>

src/UI/components/Instruction.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<script lang="ts">
22
import { setContext } from "svelte";
3-
import { $t as t } from "@lib/common/i18n";
3+
import { translateIfAvailable } from "@lib/common/i18n";
44
55
type Props = {
66
children?: () => any;
77
message?: string;
88
};
99
const { children, message }: Props = $props();
10-
const translatedMessage = $derived.by(() => (message ? t(message) : ""));
10+
const translatedMessage = $derived.by(() => (message ? translateIfAvailable(message) : ""));
1111
</script>
1212

1313
<div class="question-container">

src/UI/components/Option.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { getContext } from "svelte";
3+
import { translateIfAvailable } from "@lib/common/i18n";
34
45
type Props = {
56
title: string;
@@ -22,13 +23,14 @@
2223
children,
2324
}: Props = $props();
2425
const actualGroup = group ?? definedGroupContext;
26+
const displayTitle = $derived.by(() => translateIfAvailable(title));
2527
</script>
2628

2729
<div class="option-container {value === selectedValue ? 'selected' : ''}">
2830
<label>
2931
<div class="choice-row">
3032
<input type="radio" bind:group={value} name={actualGroup} value={selectedValue} />
31-
<span class="choice-title">{title}</span>
33+
<span class="choice-title">{displayTitle}</span>
3234
</div>
3335
<div class="choice-notes">
3436
{#if value === selectedValue && noteOnSelected}

0 commit comments

Comments
 (0)