@celestia-island/hikari 0.41.0 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/HkAffixPicker.scss +5 -0
- package/src/i18n/context.flatKeys.test.ts +40 -0
- package/src/i18n/context.ts +13 -4
- package/src/i18n/flatKeyParity.test.ts +45 -0
- package/src/i18n/locales/ar/components.json +19 -1
- package/src/i18n/locales/de/components.json +19 -1
- package/src/i18n/locales/es/components.json +19 -1
- package/src/i18n/locales/fr/components.json +19 -1
- package/src/i18n/locales/ja/components.json +19 -1
- package/src/i18n/locales/ko/components.json +19 -1
- package/src/i18n/locales/pt/components.json +19 -1
- package/src/i18n/locales/ru/components.json +19 -1
package/package.json
CHANGED
|
@@ -134,6 +134,11 @@
|
|
|
134
134
|
&[data-unset] {
|
|
135
135
|
font-style: italic;
|
|
136
136
|
color: rgb(var(--color-muted));
|
|
137
|
+
/* Italic final strokes lean past the glyph advance; overflow:hidden
|
|
138
|
+
* (the ellipsis pair above) shaves them at the content edge. The
|
|
139
|
+
* inline-end cushion is clipping room, and because it sits INSIDE
|
|
140
|
+
* the padding box it never triggers the ellipsis for fitting text. */
|
|
141
|
+
padding-inline-end: 0.2em;
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { setLocale, useI18n } from "./context";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Regression (2026-09-08 user report): the language tag's "Not set"
|
|
7
|
+
* placeholder stayed ENGLISH on a Chinese UI. Root cause —
|
|
8
|
+
* buildLocaleMessages only merged nested domain sections and silently
|
|
9
|
+
* DROPPED every top-level flat key ("hikari::x.y": "…"), so all
|
|
10
|
+
* post-0.3 flat additions (affixPicker.*, messageBox.*,
|
|
11
|
+
* localizedInput.noMatches, …) fell back to the inline English default
|
|
12
|
+
* at runtime on every locale. These tests pin flat-key loading.
|
|
13
|
+
*/
|
|
14
|
+
describe("i18n flat-key loading", () => {
|
|
15
|
+
it("serves top-level flat keys from the locale bundle", async () => {
|
|
16
|
+
await setLocale("zh-Hans");
|
|
17
|
+
const { t } = useI18n();
|
|
18
|
+
expect(t("hikari::affixPicker.unset", "Not set")).toBe("未设置");
|
|
19
|
+
expect(t("hikari::affixPicker.remove", "Remove")).toBe("移除");
|
|
20
|
+
expect(t("hikari::affixPicker.removeConfirmTitle", "Remove entry")).toBe("移除条目");
|
|
21
|
+
expect(t("hikari::messageBox.confirm", "Confirm")).toBe("确认");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("keeps nested legacy sections loading beside the flat keys", async () => {
|
|
25
|
+
await setLocale("zh-Hans");
|
|
26
|
+
const { t } = useI18n();
|
|
27
|
+
expect(t("hikari::localizedInput.chooseLanguage", "Choose editing language")).toBe(
|
|
28
|
+
"选择编辑语言",
|
|
29
|
+
);
|
|
30
|
+
expect(t("hikari::modal.close", "Close")).toBe("关闭");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("other locales serve their own flat translations, not English", async () => {
|
|
34
|
+
await setLocale("ja");
|
|
35
|
+
const { t } = useI18n();
|
|
36
|
+
expect(t("hikari::affixPicker.unset", "Not set")).toBe("未設定");
|
|
37
|
+
await setLocale("ko");
|
|
38
|
+
expect(t("hikari::affixPicker.unset", "Not set")).toBe("미설정");
|
|
39
|
+
});
|
|
40
|
+
});
|
package/src/i18n/context.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { reactive } from "vue";
|
|
|
3
3
|
type Messages = Record<string, string>;
|
|
4
4
|
|
|
5
5
|
interface LocaleModule {
|
|
6
|
-
default: Record<string,
|
|
6
|
+
default: Record<string, unknown>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const localeModules = import.meta.glob<LocaleModule>(
|
|
@@ -18,9 +18,18 @@ function buildLocaleMessages(locale: string): Messages {
|
|
|
18
18
|
for (const [path, mod] of Object.entries(localeModules)) {
|
|
19
19
|
if (!path.startsWith(prefix)) continue;
|
|
20
20
|
const domain = mod.default;
|
|
21
|
-
for (const [
|
|
22
|
-
if (typeof
|
|
23
|
-
|
|
21
|
+
for (const [key, value] of Object.entries(domain)) {
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
// Flat leaf — the appended-at-the-bottom style
|
|
24
|
+
// ("hikari::x.y": "…") every post-0.3 locale addition uses
|
|
25
|
+
// (affixPicker.*, messageBox.*, localizedInput.noMatches, …).
|
|
26
|
+
// The old object-only merge SILENTLY DROPPED these, so every
|
|
27
|
+
// flat key fell back to English at runtime no matter the
|
|
28
|
+
// locale (2026-09-08 user report: "Not set" stayed English).
|
|
29
|
+
merged[key] = value;
|
|
30
|
+
} else if (value !== null && typeof value === "object") {
|
|
31
|
+
// Legacy nested domain section ("components", "chat", …).
|
|
32
|
+
Object.assign(merged, value as Messages);
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flat-key parity guard (2026-09-10, follow-up to the flat-loader fix):
|
|
5
|
+
* buildLocaleMessages now serves top-level flat keys ("hikari::x.y": "…")
|
|
6
|
+
* beside the legacy nested sections — but a key missing from a locale's
|
|
7
|
+
* components.json still silently falls back to English for that locale.
|
|
8
|
+
* Every flat key in the en bundle must therefore exist in ALL 11 locale
|
|
9
|
+
* bundles. (This is the guard that would have caught the affixPicker/
|
|
10
|
+
* messageBox flat family missing from 8 locales for months.)
|
|
11
|
+
*/
|
|
12
|
+
const modules = import.meta.glob<{ default: Record<string, unknown> }>(
|
|
13
|
+
"./locales/*/components.json",
|
|
14
|
+
{ eager: true },
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const EXPECTED_LOCALES = [
|
|
18
|
+
"ar", "de", "en", "es", "fr", "ja", "ko", "pt", "ru", "zh-Hans", "zh-Hant",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
describe("components.json flat-key parity", () => {
|
|
22
|
+
const bundles = Object.entries(modules).map(([path, mod]) => ({
|
|
23
|
+
locale: path.match(/locales\/([^/]+)\/components\.json/)?.[1] ?? "",
|
|
24
|
+
flat: Object.fromEntries(
|
|
25
|
+
Object.entries(mod.default).filter(([, v]) => typeof v === "string"),
|
|
26
|
+
) as Record<string, string>,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
it("covers all 11 locales", () => {
|
|
30
|
+
expect(bundles.map((b) => b.locale).sort()).toEqual(EXPECTED_LOCALES);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("defines every en flat key in every locale, non-empty", () => {
|
|
34
|
+
const en = bundles.find((b) => b.locale === "en");
|
|
35
|
+
expect(en, "en bundle renders").toBeTruthy();
|
|
36
|
+
const keys = Object.keys(en!.flat);
|
|
37
|
+
expect(keys.length, "en has flat keys").toBeGreaterThan(0);
|
|
38
|
+
for (const { locale, flat } of bundles) {
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
expect(typeof flat[key], `${locale} must define ${key}`).toBe("string");
|
|
41
|
+
expect((flat[key] ?? "").length, `${locale} ${key} must not be empty`).toBeGreaterThan(0);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "إعادة تعيين التكبير",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "تكبير",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "تصغير"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "غير محدد",
|
|
187
|
+
"hikari::affixPicker.title": "الخيارات",
|
|
188
|
+
"hikari::affixPicker.search": "بحث",
|
|
189
|
+
"hikari::affixPicker.empty": "لا تطابقات",
|
|
190
|
+
"hikari::affixPicker.selected": "المحدد",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "تم اختيار {count}",
|
|
192
|
+
"hikari::affixPicker.switchTo": "التبديل إلى",
|
|
193
|
+
"hikari::affixPicker.remove": "إزالة",
|
|
194
|
+
"hikari::affixPicker.useCustom": "استخدام \"{query}\"",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "إزالة العنصر",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "إزالة \"{label}\"؟ لا يمكن التراجع عن هذا الإجراء.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "لا توجد لغة مطابقة",
|
|
198
|
+
"hikari::messageBox.confirm": "تأكيد",
|
|
199
|
+
"hikari::messageBox.cancel": "إلغاء",
|
|
200
|
+
"hikari::messageBox.ok": "موافق",
|
|
201
|
+
"hikari::messageBox.alertTitle": "تنبيه",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "يرجى التأكيد",
|
|
203
|
+
"hikari::messageBox.promptTitle": "إدخال"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "Zoom zurücksetzen",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "Vergrößern",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "Verkleinern"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "Nicht festgelegt",
|
|
187
|
+
"hikari::affixPicker.title": "Optionen",
|
|
188
|
+
"hikari::affixPicker.search": "Suchen",
|
|
189
|
+
"hikari::affixPicker.empty": "Keine Treffer",
|
|
190
|
+
"hikari::affixPicker.selected": "Ausgewählt",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count} ausgewählt",
|
|
192
|
+
"hikari::affixPicker.switchTo": "Wechseln zu",
|
|
193
|
+
"hikari::affixPicker.remove": "Entfernen",
|
|
194
|
+
"hikari::affixPicker.useCustom": "„{query}“ verwenden",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "Eintrag entfernen",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "„{label}“ entfernen? Dies kann nicht rückgängig gemacht werden.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "Keine passende Sprache",
|
|
198
|
+
"hikari::messageBox.confirm": "Bestätigen",
|
|
199
|
+
"hikari::messageBox.cancel": "Abbrechen",
|
|
200
|
+
"hikari::messageBox.ok": "OK",
|
|
201
|
+
"hikari::messageBox.alertTitle": "Hinweis",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "Bitte bestätigen",
|
|
203
|
+
"hikari::messageBox.promptTitle": "Eingabe"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "Restablecer zoom",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "Acercar",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "Alejar"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "Sin establecer",
|
|
187
|
+
"hikari::affixPicker.title": "Opciones",
|
|
188
|
+
"hikari::affixPicker.search": "Buscar",
|
|
189
|
+
"hikari::affixPicker.empty": "Sin coincidencias",
|
|
190
|
+
"hikari::affixPicker.selected": "Seleccionados",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count} seleccionados",
|
|
192
|
+
"hikari::affixPicker.switchTo": "Cambiar a",
|
|
193
|
+
"hikari::affixPicker.remove": "Quitar",
|
|
194
|
+
"hikari::affixPicker.useCustom": "Usar «{query}»",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "Quitar elemento",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "¿Quitar «{label}»? Esto no se puede deshacer.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "Ningún idioma coincidente",
|
|
198
|
+
"hikari::messageBox.confirm": "Confirmar",
|
|
199
|
+
"hikari::messageBox.cancel": "Cancelar",
|
|
200
|
+
"hikari::messageBox.ok": "Aceptar",
|
|
201
|
+
"hikari::messageBox.alertTitle": "Aviso",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "Confirme",
|
|
203
|
+
"hikari::messageBox.promptTitle": "Entrada"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "Réinitialiser le zoom",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "Agrandir",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "Réduire"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "Non défini",
|
|
187
|
+
"hikari::affixPicker.title": "Options",
|
|
188
|
+
"hikari::affixPicker.search": "Rechercher",
|
|
189
|
+
"hikari::affixPicker.empty": "Aucun résultat",
|
|
190
|
+
"hikari::affixPicker.selected": "Sélectionnés",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count} sélectionnés",
|
|
192
|
+
"hikari::affixPicker.switchTo": "Passer à",
|
|
193
|
+
"hikari::affixPicker.remove": "Retirer",
|
|
194
|
+
"hikari::affixPicker.useCustom": "Utiliser « {query} »",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "Retirer l’entrée",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "Retirer « {label} » ? Cette action est irréversible.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "Aucune langue correspondante",
|
|
198
|
+
"hikari::messageBox.confirm": "Confirmer",
|
|
199
|
+
"hikari::messageBox.cancel": "Annuler",
|
|
200
|
+
"hikari::messageBox.ok": "OK",
|
|
201
|
+
"hikari::messageBox.alertTitle": "Avis",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "Veuillez confirmer",
|
|
203
|
+
"hikari::messageBox.promptTitle": "Saisie"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "ズームをリセット",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "拡大",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "縮小"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "未設定",
|
|
187
|
+
"hikari::affixPicker.title": "オプション",
|
|
188
|
+
"hikari::affixPicker.search": "検索",
|
|
189
|
+
"hikari::affixPicker.empty": "一致なし",
|
|
190
|
+
"hikari::affixPicker.selected": "選択中",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count} 件選択中",
|
|
192
|
+
"hikari::affixPicker.switchTo": "切り替え",
|
|
193
|
+
"hikari::affixPicker.remove": "削除",
|
|
194
|
+
"hikari::affixPicker.useCustom": "「{query}」を使用",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "項目を削除",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "「{label}」を削除しますか?この操作は元に戻せません。",
|
|
197
|
+
"hikari::localizedInput.noMatches": "一致する言語がありません",
|
|
198
|
+
"hikari::messageBox.confirm": "確認",
|
|
199
|
+
"hikari::messageBox.cancel": "キャンセル",
|
|
200
|
+
"hikari::messageBox.ok": "OK",
|
|
201
|
+
"hikari::messageBox.alertTitle": "お知らせ",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "確認してください",
|
|
203
|
+
"hikari::messageBox.promptTitle": "入力"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "줌 초기화",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "확대",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "축소"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "미설정",
|
|
187
|
+
"hikari::affixPicker.title": "옵션",
|
|
188
|
+
"hikari::affixPicker.search": "검색",
|
|
189
|
+
"hikari::affixPicker.empty": "일치 항목 없음",
|
|
190
|
+
"hikari::affixPicker.selected": "선택됨",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count}개 선택됨",
|
|
192
|
+
"hikari::affixPicker.switchTo": "전환",
|
|
193
|
+
"hikari::affixPicker.remove": "제거",
|
|
194
|
+
"hikari::affixPicker.useCustom": "\"{query}\" 사용",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "항목 제거",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "\"{label}\"을(를) 제거할까요? 이 작업은 되돌릴 수 없습니다.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "일치하는 언어가 없습니다",
|
|
198
|
+
"hikari::messageBox.confirm": "확인",
|
|
199
|
+
"hikari::messageBox.cancel": "취소",
|
|
200
|
+
"hikari::messageBox.ok": "확인",
|
|
201
|
+
"hikari::messageBox.alertTitle": "알림",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "확인 필요",
|
|
203
|
+
"hikari::messageBox.promptTitle": "입력"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "Redefinir zoom",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "Ampliar",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "Reduzir"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "Não definido",
|
|
187
|
+
"hikari::affixPicker.title": "Opções",
|
|
188
|
+
"hikari::affixPicker.search": "Pesquisar",
|
|
189
|
+
"hikari::affixPicker.empty": "Sem correspondências",
|
|
190
|
+
"hikari::affixPicker.selected": "Selecionados",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "{count} selecionados",
|
|
192
|
+
"hikari::affixPicker.switchTo": "Mudar para",
|
|
193
|
+
"hikari::affixPicker.remove": "Remover",
|
|
194
|
+
"hikari::affixPicker.useCustom": "Usar \"{query}\"",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "Remover item",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "Remover \"{label}\"? Isso não pode ser desfeito.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "Nenhum idioma correspondente",
|
|
198
|
+
"hikari::messageBox.confirm": "Confirmar",
|
|
199
|
+
"hikari::messageBox.cancel": "Cancelar",
|
|
200
|
+
"hikari::messageBox.ok": "OK",
|
|
201
|
+
"hikari::messageBox.alertTitle": "Aviso",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "Confirme",
|
|
203
|
+
"hikari::messageBox.promptTitle": "Entrada"
|
|
186
204
|
}
|
|
@@ -182,5 +182,23 @@
|
|
|
182
182
|
"hikari::zoomToolbar.reset": "Сбросить масштаб",
|
|
183
183
|
"hikari::zoomToolbar.zoomIn": "Увеличить",
|
|
184
184
|
"hikari::zoomToolbar.zoomOut": "Уменьшить"
|
|
185
|
-
}
|
|
185
|
+
},
|
|
186
|
+
"hikari::affixPicker.unset": "Не задано",
|
|
187
|
+
"hikari::affixPicker.title": "Параметры",
|
|
188
|
+
"hikari::affixPicker.search": "Поиск",
|
|
189
|
+
"hikari::affixPicker.empty": "Нет совпадений",
|
|
190
|
+
"hikari::affixPicker.selected": "Выбрано",
|
|
191
|
+
"hikari::affixPicker.selectedCount": "Выбрано: {count}",
|
|
192
|
+
"hikari::affixPicker.switchTo": "Переключиться на",
|
|
193
|
+
"hikari::affixPicker.remove": "Удалить",
|
|
194
|
+
"hikari::affixPicker.useCustom": "Использовать «{query}»",
|
|
195
|
+
"hikari::affixPicker.removeConfirmTitle": "Удалить запись",
|
|
196
|
+
"hikari::affixPicker.removeConfirm": "Удалить «{label}»? Это действие нельзя отменить.",
|
|
197
|
+
"hikari::localizedInput.noMatches": "Нет подходящего языка",
|
|
198
|
+
"hikari::messageBox.confirm": "Подтвердить",
|
|
199
|
+
"hikari::messageBox.cancel": "Отмена",
|
|
200
|
+
"hikari::messageBox.ok": "ОК",
|
|
201
|
+
"hikari::messageBox.alertTitle": "Уведомление",
|
|
202
|
+
"hikari::messageBox.confirmTitle": "Подтвердите",
|
|
203
|
+
"hikari::messageBox.promptTitle": "Ввод"
|
|
186
204
|
}
|