feat(i18n): standardize bilingual catalogs and tooling
This commit is contained in:
@@ -4,16 +4,16 @@ import { describe, expect, it } from "vitest"
|
||||
|
||||
import { AnsiText, resolveAnsiComponent } from "./ansi-text"
|
||||
|
||||
function TestComponent({ children }: { children?: string }) {
|
||||
return <code>{children}</code>
|
||||
}
|
||||
|
||||
describe("AnsiText", () => {
|
||||
it("resolves direct, CommonJS, and nested default exports", () => {
|
||||
const Component = ({ children }: { children?: string }) => (
|
||||
<code>{children}</code>
|
||||
)
|
||||
|
||||
expect(resolveAnsiComponent(Component)).toBe(Component)
|
||||
expect(resolveAnsiComponent({ default: Component })).toBe(Component)
|
||||
expect(resolveAnsiComponent({ default: { default: Component } })).toBe(
|
||||
Component
|
||||
expect(resolveAnsiComponent(TestComponent)).toBe(TestComponent)
|
||||
expect(resolveAnsiComponent({ default: TestComponent })).toBe(TestComponent)
|
||||
expect(resolveAnsiComponent({ default: { default: TestComponent } })).toBe(
|
||||
TestComponent
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export function DevtoolApp() {
|
||||
|
||||
const locale =
|
||||
state.project.locales.find(
|
||||
(locale) => locale !== state.project.sourceLocale
|
||||
(candidateLocale) => candidateLocale !== state.project.sourceLocale
|
||||
) ?? state.project.sourceLocale
|
||||
|
||||
if (!state.project.locales.includes(locale)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { StrictMode } from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
|
||||
import { DevtoolApp } from "./app"
|
||||
// oxlint-disable-next-line import/no-unassigned-import -- Loads the standalone Devtool stylesheet.
|
||||
import "./styles.css"
|
||||
|
||||
const root = document.getElementById("root")
|
||||
|
||||
@@ -5,17 +5,18 @@ import { resolveI18nDevtoolLocale } from "./devtool-localization"
|
||||
describe("resolveI18nDevtoolLocale", () => {
|
||||
it("normalizes common BCP 47 language tags", () => {
|
||||
expect(resolveI18nDevtoolLocale("zh-CN")).toBe("zh-Hans")
|
||||
expect(resolveI18nDevtoolLocale("zh-TW")).toBe("zh-Hant")
|
||||
expect(resolveI18nDevtoolLocale("es-419")).toBe("es")
|
||||
expect(resolveI18nDevtoolLocale("en-GB")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("selects the first supported preferred locale", () => {
|
||||
expect(
|
||||
resolveI18nDevtoolLocale(undefined, ["pt-BR", "fr-CA", "en-US"])
|
||||
).toBe("fr")
|
||||
resolveI18nDevtoolLocale(undefined, ["pt-BR", "fr-CA", "zh-CN"])
|
||||
).toBe("zh-Hans")
|
||||
})
|
||||
|
||||
it("falls back to English", () => {
|
||||
expect(resolveI18nDevtoolLocale(undefined, ["pt-BR"])).toBe("en")
|
||||
expect(resolveI18nDevtoolLocale(undefined, ["pt-BR", "zh-TW"])).toBe(
|
||||
"en-US"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
export const I18N_DEVTOOL_LOCALES = [
|
||||
"en",
|
||||
"de",
|
||||
"es",
|
||||
"fr",
|
||||
"ja",
|
||||
"ko",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
] as const
|
||||
export const I18N_DEVTOOL_LOCALES = ["en-US", "zh-Hans"] as const
|
||||
|
||||
export type I18nDevtoolLocale = (typeof I18N_DEVTOOL_LOCALES)[number]
|
||||
|
||||
@@ -104,7 +95,7 @@ interface DevtoolMessages {
|
||||
}
|
||||
|
||||
const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
en: {
|
||||
"en-US": {
|
||||
actions: {
|
||||
compile: "Compile",
|
||||
compileCompleted: "Compilation completed.",
|
||||
@@ -195,460 +186,6 @@ const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
open: "Open I18n Devtool",
|
||||
},
|
||||
},
|
||||
de: {
|
||||
actions: {
|
||||
compile: "Kompilieren",
|
||||
compileCompleted: "Kompilierung abgeschlossen.",
|
||||
compileFailed: "Kompilierung fehlgeschlagen.",
|
||||
compiling: "Wird kompiliert…",
|
||||
enterFullscreen: "Vollbildmodus aktivieren",
|
||||
exitFullscreen: "Vollbildmodus beenden",
|
||||
extract: "Extrahieren",
|
||||
extractCompleted: "Extraktion abgeschlossen.",
|
||||
extractFailed: "Extraktion fehlgeschlagen.",
|
||||
extracting: "Wird extrahiert…",
|
||||
label: "Aktionen",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Befehlsausgabe schließen",
|
||||
collapse: "Befehlsausgabe einklappen",
|
||||
expand: "Befehlsausgabe ausklappen",
|
||||
title: "Befehlsausgabe",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Zielsprache der Nachrichten",
|
||||
label: "Zielsprache",
|
||||
noTargetLocales: "Keine Zielsprachen konfiguriert.",
|
||||
sourceLabel: "Ausgangssprache",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `${count} weitere Quellorte`,
|
||||
chooseCopyContent: "Inhalt zum Kopieren auswählen",
|
||||
collapseNamespace: (path) => `${path} einklappen`,
|
||||
collapsePackage: (packageName) => `${packageName} einklappen`,
|
||||
copied: "Kopiert",
|
||||
copyDescriptor: "Nachrichten-Deskriptor kopieren",
|
||||
copyFailed: "Kopieren fehlgeschlagen",
|
||||
copyMessageId: "Nachrichten-ID kopieren",
|
||||
copySource: "Quelltext kopieren",
|
||||
copySourceLocation: "Quellort kopieren",
|
||||
copyTranslation: "Aktuelle Übersetzung kopieren",
|
||||
couldNotSave: "Speichern fehlgeschlagen",
|
||||
emptyCatalog:
|
||||
"Es sind noch keine Nachrichten verfügbar. Extrahieren Sie das Projekt, um diesen Katalog zu füllen.",
|
||||
emptyPackage: "In diesem Paket wurden keine Nachrichten gefunden.",
|
||||
expandNamespace: (path) => `${path} ausklappen`,
|
||||
expandPackage: (packageName) => `${packageName} ausklappen`,
|
||||
loading: (locale) => `${locale} wird geladen…`,
|
||||
messageCount: (count) => `${count} Nachrichten`,
|
||||
messageEditor: "Nachrichteneditor",
|
||||
messageNavigation: "Nachrichtennavigation",
|
||||
missingCount: (count) => `${count} fehlen`,
|
||||
missingOnly: "Nur fehlende",
|
||||
noMatches: "Keine Nachrichten entsprechen den aktuellen Filtern.",
|
||||
noMissingMessages: "Alle Nachrichten sind übersetzt.",
|
||||
noSearchResults: "Keine Nachrichten entsprechen Ihrer Suche.",
|
||||
reset: "Zurücksetzen",
|
||||
retry: "Erneut versuchen",
|
||||
save: "Speichern",
|
||||
saved: "Gespeichert",
|
||||
saving: "Wird gespeichert…",
|
||||
search: "Nachrichten durchsuchen",
|
||||
selectMessage:
|
||||
"Wählen Sie eine Nachricht aus, um ihre Übersetzung anzuzeigen und zu bearbeiten.",
|
||||
source: "Quelle",
|
||||
sourceLocations: "Quellorte",
|
||||
translation: "Übersetzung",
|
||||
translationFor: (id, locale) => `${id}: Übersetzung für ${locale}`,
|
||||
unknownSourceLocation: "Unbekannter Quellort",
|
||||
},
|
||||
settings: {
|
||||
auto: "Automatisch",
|
||||
autoTheme: "Automatisches Design",
|
||||
center: "Mitte",
|
||||
dark: "Dunkel",
|
||||
darkTheme: "Dunkles Design",
|
||||
interfaceLanguage: "Oberflächensprache",
|
||||
interfaceLanguageControl: "Oberflächensprache des I18n Devtools",
|
||||
label: "I18n-Devtool-Einstellungen",
|
||||
left: "Links",
|
||||
light: "Hell",
|
||||
lightTheme: "Helles Design",
|
||||
open: "I18n-Devtool-Einstellungen öffnen",
|
||||
panelPosition: "Panelposition",
|
||||
panelPositionControl: "Position des I18n-Devtool-Panels",
|
||||
right: "Rechts",
|
||||
theme: "Design",
|
||||
themeControl: "I18n-Devtool-Design",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool schließen",
|
||||
open: "I18n Devtool öffnen",
|
||||
},
|
||||
},
|
||||
es: {
|
||||
actions: {
|
||||
compile: "Compilar",
|
||||
compileCompleted: "Compilación completada.",
|
||||
compileFailed: "Error de compilación.",
|
||||
compiling: "Compilando…",
|
||||
enterFullscreen: "Entrar en pantalla completa",
|
||||
exitFullscreen: "Salir de pantalla completa",
|
||||
extract: "Extraer",
|
||||
extractCompleted: "Extracción completada.",
|
||||
extractFailed: "Error de extracción.",
|
||||
extracting: "Extrayendo…",
|
||||
label: "Acciones",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Cerrar salida del comando",
|
||||
collapse: "Contraer salida del comando",
|
||||
expand: "Expandir salida del comando",
|
||||
title: "Salida del comando",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Idioma de destino de los mensajes",
|
||||
label: "Idioma de destino",
|
||||
noTargetLocales: "No hay idiomas de destino configurados.",
|
||||
sourceLabel: "Idioma de origen",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) =>
|
||||
`${count} ubicaciones de origen adicionales`,
|
||||
chooseCopyContent: "Elegir contenido para copiar",
|
||||
collapseNamespace: (path) => `Contraer ${path}`,
|
||||
collapsePackage: (packageName) => `Contraer ${packageName}`,
|
||||
copied: "Copiado",
|
||||
copyDescriptor: "Copiar descriptor del mensaje",
|
||||
copyFailed: "No se pudo copiar",
|
||||
copyMessageId: "Copiar ID del mensaje",
|
||||
copySource: "Copiar texto de origen",
|
||||
copySourceLocation: "Copiar ubicación de origen",
|
||||
copyTranslation: "Copiar traducción actual",
|
||||
couldNotSave: "No se pudo guardar",
|
||||
emptyCatalog:
|
||||
"Aún no hay mensajes disponibles. Extraiga el proyecto para completar este catálogo.",
|
||||
emptyPackage: "No se encontraron mensajes en este paquete.",
|
||||
expandNamespace: (path) => `Expandir ${path}`,
|
||||
expandPackage: (packageName) => `Expandir ${packageName}`,
|
||||
loading: (locale) => `Cargando ${locale}…`,
|
||||
messageCount: (count) => `${count} mensajes`,
|
||||
messageEditor: "Editor de mensajes",
|
||||
messageNavigation: "Navegación de mensajes",
|
||||
missingCount: (count) => `${count} faltantes`,
|
||||
missingOnly: "Solo faltantes",
|
||||
noMatches: "Ningún mensaje coincide con los filtros actuales.",
|
||||
noMissingMessages: "Todos los mensajes tienen traducción.",
|
||||
noSearchResults: "Ningún mensaje coincide con la búsqueda.",
|
||||
reset: "Restablecer",
|
||||
retry: "Reintentar",
|
||||
save: "Guardar",
|
||||
saved: "Guardado",
|
||||
saving: "Guardando…",
|
||||
search: "Buscar mensajes",
|
||||
selectMessage: "Seleccione un mensaje para ver y editar su traducción.",
|
||||
source: "Origen",
|
||||
sourceLocations: "Ubicaciones de origen",
|
||||
translation: "Traducción",
|
||||
translationFor: (id, locale) => `Traducción de ${id} para ${locale}`,
|
||||
unknownSourceLocation: "Ubicación de origen desconocida",
|
||||
},
|
||||
settings: {
|
||||
auto: "Automático",
|
||||
autoTheme: "Tema automático",
|
||||
center: "Centro",
|
||||
dark: "Oscuro",
|
||||
darkTheme: "Tema oscuro",
|
||||
interfaceLanguage: "Idioma de la interfaz",
|
||||
interfaceLanguageControl: "Idioma de la interfaz de I18n Devtool",
|
||||
label: "Ajustes de I18n Devtool",
|
||||
left: "Izquierda",
|
||||
light: "Claro",
|
||||
lightTheme: "Tema claro",
|
||||
open: "Abrir ajustes de I18n Devtool",
|
||||
panelPosition: "Posición del panel",
|
||||
panelPositionControl: "Posición del panel de I18n Devtool",
|
||||
right: "Derecha",
|
||||
theme: "Tema",
|
||||
themeControl: "Tema de I18n Devtool",
|
||||
},
|
||||
trigger: {
|
||||
close: "Cerrar I18n Devtool",
|
||||
open: "Abrir I18n Devtool",
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
actions: {
|
||||
compile: "Compiler",
|
||||
compileCompleted: "Compilation terminée.",
|
||||
compileFailed: "Échec de la compilation.",
|
||||
compiling: "Compilation…",
|
||||
enterFullscreen: "Passer en plein écran",
|
||||
exitFullscreen: "Quitter le plein écran",
|
||||
extract: "Extraire",
|
||||
extractCompleted: "Extraction terminée.",
|
||||
extractFailed: "Échec de l’extraction.",
|
||||
extracting: "Extraction…",
|
||||
label: "Actions",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "Fermer la sortie de commande",
|
||||
collapse: "Réduire la sortie de commande",
|
||||
expand: "Développer la sortie de commande",
|
||||
title: "Sortie de commande",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "Langue cible des messages",
|
||||
label: "Langue cible",
|
||||
noTargetLocales: "Aucune langue cible n’est configurée.",
|
||||
sourceLabel: "Langue source",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) =>
|
||||
`${count} emplacements source supplémentaires`,
|
||||
chooseCopyContent: "Choisir le contenu à copier",
|
||||
collapseNamespace: (path) => `Réduire ${path}`,
|
||||
collapsePackage: (packageName) => `Réduire ${packageName}`,
|
||||
copied: "Copié",
|
||||
copyDescriptor: "Copier le descripteur du message",
|
||||
copyFailed: "Échec de la copie",
|
||||
copyMessageId: "Copier l’identifiant du message",
|
||||
copySource: "Copier le texte source",
|
||||
copySourceLocation: "Copier l’emplacement source",
|
||||
copyTranslation: "Copier la traduction actuelle",
|
||||
couldNotSave: "Échec de l’enregistrement",
|
||||
emptyCatalog:
|
||||
"Aucun message n’est encore disponible. Extrayez le projet pour remplir ce catalogue.",
|
||||
emptyPackage: "Aucun message n’a été trouvé dans ce package.",
|
||||
expandNamespace: (path) => `Développer ${path}`,
|
||||
expandPackage: (packageName) => `Développer ${packageName}`,
|
||||
loading: (locale) => `Chargement de ${locale}…`,
|
||||
messageCount: (count) => `${count} messages`,
|
||||
messageEditor: "Éditeur de messages",
|
||||
messageNavigation: "Navigation des messages",
|
||||
missingCount: (count) => `${count} manquants`,
|
||||
missingOnly: "Manquants uniquement",
|
||||
noMatches: "Aucun message ne correspond aux filtres actuels.",
|
||||
noMissingMessages: "Tous les messages sont traduits.",
|
||||
noSearchResults: "Aucun message ne correspond à votre recherche.",
|
||||
reset: "Réinitialiser",
|
||||
retry: "Réessayer",
|
||||
save: "Enregistrer",
|
||||
saved: "Enregistré",
|
||||
saving: "Enregistrement…",
|
||||
search: "Rechercher des messages",
|
||||
selectMessage:
|
||||
"Sélectionnez un message pour afficher et modifier sa traduction.",
|
||||
source: "Source",
|
||||
sourceLocations: "Emplacements source",
|
||||
translation: "Traduction",
|
||||
translationFor: (id, locale) => `Traduction de ${id} pour ${locale}`,
|
||||
unknownSourceLocation: "Emplacement source inconnu",
|
||||
},
|
||||
settings: {
|
||||
auto: "Auto",
|
||||
autoTheme: "Thème automatique",
|
||||
center: "Centre",
|
||||
dark: "Sombre",
|
||||
darkTheme: "Thème sombre",
|
||||
interfaceLanguage: "Langue de l’interface",
|
||||
interfaceLanguageControl: "Langue de l’interface de l’outil I18n",
|
||||
label: "Paramètres de I18n Devtool",
|
||||
left: "Gauche",
|
||||
light: "Clair",
|
||||
lightTheme: "Thème clair",
|
||||
open: "Ouvrir les paramètres de I18n Devtool",
|
||||
panelPosition: "Position du panneau",
|
||||
panelPositionControl: "Position du panneau I18n Devtool",
|
||||
right: "Droite",
|
||||
theme: "Thème",
|
||||
themeControl: "Thème de I18n Devtool",
|
||||
},
|
||||
trigger: {
|
||||
close: "Fermer I18n Devtool",
|
||||
open: "Ouvrir I18n Devtool",
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
actions: {
|
||||
compile: "コンパイル",
|
||||
compileCompleted: "コンパイルが完了しました。",
|
||||
compileFailed: "コンパイルに失敗しました。",
|
||||
compiling: "コンパイル中…",
|
||||
enterFullscreen: "全画面表示にする",
|
||||
exitFullscreen: "全画面表示を終了",
|
||||
extract: "抽出",
|
||||
extractCompleted: "抽出が完了しました。",
|
||||
extractFailed: "抽出に失敗しました。",
|
||||
extracting: "抽出中…",
|
||||
label: "操作",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "コマンド出力を閉じる",
|
||||
collapse: "コマンド出力を折りたたむ",
|
||||
expand: "コマンド出力を展開する",
|
||||
title: "コマンド出力",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "メッセージの翻訳先言語",
|
||||
label: "翻訳先言語",
|
||||
noTargetLocales: "翻訳先言語が設定されていません。",
|
||||
sourceLabel: "原文言語",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `他 ${count} 件のソース位置`,
|
||||
chooseCopyContent: "コピーする内容を選択",
|
||||
collapseNamespace: (path) => `${path} を折りたたむ`,
|
||||
collapsePackage: (packageName) => `${packageName} を折りたたむ`,
|
||||
copied: "コピーしました",
|
||||
copyDescriptor: "メッセージ記述子をコピー",
|
||||
copyFailed: "コピーできませんでした",
|
||||
copyMessageId: "メッセージ ID をコピー",
|
||||
copySource: "原文をコピー",
|
||||
copySourceLocation: "ソース位置をコピー",
|
||||
copyTranslation: "現在の翻訳をコピー",
|
||||
couldNotSave: "保存できませんでした",
|
||||
emptyCatalog:
|
||||
"メッセージはまだありません。プロジェクトを抽出してカタログを作成してください。",
|
||||
emptyPackage: "このパッケージにはメッセージがありません。",
|
||||
expandNamespace: (path) => `${path} を展開`,
|
||||
expandPackage: (packageName) => `${packageName} を展開`,
|
||||
loading: (locale) => `${locale} を読み込み中…`,
|
||||
messageCount: (count) => `${count} 件`,
|
||||
messageEditor: "メッセージエディター",
|
||||
messageNavigation: "メッセージナビゲーション",
|
||||
missingCount: (count) => `未翻訳 ${count} 件`,
|
||||
missingOnly: "未翻訳のみ",
|
||||
noMatches: "現在のフィルターに一致するメッセージはありません。",
|
||||
noMissingMessages: "すべてのメッセージが翻訳済みです。",
|
||||
noSearchResults: "検索に一致するメッセージはありません。",
|
||||
reset: "リセット",
|
||||
retry: "再試行",
|
||||
save: "保存",
|
||||
saved: "保存済み",
|
||||
saving: "保存中…",
|
||||
search: "メッセージを検索",
|
||||
selectMessage: "翻訳を表示・編集するメッセージを選択してください。",
|
||||
source: "原文",
|
||||
sourceLocations: "ソース位置",
|
||||
translation: "翻訳",
|
||||
translationFor: (id, locale) => `${id} の ${locale} 翻訳`,
|
||||
unknownSourceLocation: "ソース位置不明",
|
||||
},
|
||||
settings: {
|
||||
auto: "自動",
|
||||
autoTheme: "自動テーマ",
|
||||
center: "中央",
|
||||
dark: "ダーク",
|
||||
darkTheme: "ダークテーマ",
|
||||
interfaceLanguage: "表示言語",
|
||||
interfaceLanguageControl: "I18n Devtool の表示言語",
|
||||
label: "I18n Devtool の設定",
|
||||
left: "左",
|
||||
light: "ライト",
|
||||
lightTheme: "ライトテーマ",
|
||||
open: "I18n Devtool の設定を開く",
|
||||
panelPosition: "パネル位置",
|
||||
panelPositionControl: "I18n Devtool のパネル位置",
|
||||
right: "右",
|
||||
theme: "テーマ",
|
||||
themeControl: "I18n Devtool のテーマ",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool を閉じる",
|
||||
open: "I18n Devtool を開く",
|
||||
},
|
||||
},
|
||||
ko: {
|
||||
actions: {
|
||||
compile: "컴파일",
|
||||
compileCompleted: "컴파일이 완료되었습니다.",
|
||||
compileFailed: "컴파일에 실패했습니다.",
|
||||
compiling: "컴파일 중…",
|
||||
enterFullscreen: "전체 화면으로 전환",
|
||||
exitFullscreen: "전체 화면 종료",
|
||||
extract: "추출",
|
||||
extractCompleted: "추출이 완료되었습니다.",
|
||||
extractFailed: "추출에 실패했습니다.",
|
||||
extracting: "추출 중…",
|
||||
label: "작업",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "명령 출력을 닫기",
|
||||
collapse: "명령 출력을 접기",
|
||||
expand: "명령 출력을 펼치기",
|
||||
title: "명령 출력",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "메시지 대상 언어",
|
||||
label: "대상 언어",
|
||||
noTargetLocales: "대상 언어가 설정되지 않았습니다.",
|
||||
sourceLabel: "원본 언어",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `추가 소스 위치 ${count}개`,
|
||||
chooseCopyContent: "복사할 내용 선택",
|
||||
collapseNamespace: (path) => `${path} 접기`,
|
||||
collapsePackage: (packageName) => `${packageName} 접기`,
|
||||
copied: "복사됨",
|
||||
copyDescriptor: "메시지 설명자 복사",
|
||||
copyFailed: "복사하지 못했습니다",
|
||||
copyMessageId: "메시지 ID 복사",
|
||||
copySource: "원문 복사",
|
||||
copySourceLocation: "소스 위치 복사",
|
||||
copyTranslation: "현재 번역 복사",
|
||||
couldNotSave: "저장하지 못했습니다",
|
||||
emptyCatalog:
|
||||
"아직 사용할 수 있는 메시지가 없습니다. 프로젝트에서 메시지를 추출해 카탈로그를 채우세요.",
|
||||
emptyPackage: "이 패키지에는 메시지가 없습니다.",
|
||||
expandNamespace: (path) => `${path} 펼치기`,
|
||||
expandPackage: (packageName) => `${packageName} 펼치기`,
|
||||
loading: (locale) => `${locale} 불러오는 중…`,
|
||||
messageCount: (count) => `${count}개 메시지`,
|
||||
messageEditor: "메시지 편집기",
|
||||
messageNavigation: "메시지 탐색",
|
||||
missingCount: (count) => `${count}개 누락`,
|
||||
missingOnly: "누락된 항목만",
|
||||
noMatches: "현재 필터와 일치하는 메시지가 없습니다.",
|
||||
noMissingMessages: "모든 메시지가 번역되었습니다.",
|
||||
noSearchResults: "검색어와 일치하는 메시지가 없습니다.",
|
||||
reset: "재설정",
|
||||
retry: "다시 시도",
|
||||
save: "저장",
|
||||
saved: "저장됨",
|
||||
saving: "저장 중…",
|
||||
search: "메시지 검색",
|
||||
selectMessage: "번역을 확인하고 편집할 메시지를 선택하세요.",
|
||||
source: "원문",
|
||||
sourceLocations: "소스 위치",
|
||||
translation: "번역",
|
||||
translationFor: (id, locale) => `${id}의 ${locale} 번역`,
|
||||
unknownSourceLocation: "알 수 없는 소스 위치",
|
||||
},
|
||||
settings: {
|
||||
auto: "자동",
|
||||
autoTheme: "자동 테마",
|
||||
center: "가운데",
|
||||
dark: "다크",
|
||||
darkTheme: "다크 테마",
|
||||
interfaceLanguage: "인터페이스 언어",
|
||||
interfaceLanguageControl: "I18n Devtool 인터페이스 언어",
|
||||
label: "I18n Devtool 설정",
|
||||
left: "왼쪽",
|
||||
light: "라이트",
|
||||
lightTheme: "라이트 테마",
|
||||
open: "I18n Devtool 설정 열기",
|
||||
panelPosition: "패널 위치",
|
||||
panelPositionControl: "I18n Devtool 패널 위치",
|
||||
right: "오른쪽",
|
||||
theme: "테마",
|
||||
themeControl: "I18n Devtool 테마",
|
||||
},
|
||||
trigger: {
|
||||
close: "I18n Devtool 닫기",
|
||||
open: "I18n Devtool 열기",
|
||||
},
|
||||
},
|
||||
"zh-Hans": {
|
||||
actions: {
|
||||
compile: "编译",
|
||||
@@ -738,103 +275,14 @@ const messagesByLocale: Record<I18nDevtoolLocale, DevtoolMessages> = {
|
||||
open: "打开 I18n 开发工具",
|
||||
},
|
||||
},
|
||||
"zh-Hant": {
|
||||
actions: {
|
||||
compile: "編譯",
|
||||
compileCompleted: "編譯完成。",
|
||||
compileFailed: "編譯失敗。",
|
||||
compiling: "正在編譯…",
|
||||
enterFullscreen: "進入全螢幕",
|
||||
exitFullscreen: "退出全螢幕",
|
||||
extract: "擷取",
|
||||
extractCompleted: "擷取完成。",
|
||||
extractFailed: "擷取失敗。",
|
||||
extracting: "正在擷取…",
|
||||
label: "操作",
|
||||
},
|
||||
commandOutput: {
|
||||
close: "關閉命令輸出",
|
||||
collapse: "收合命令輸出",
|
||||
expand: "展開命令輸出",
|
||||
title: "命令輸出",
|
||||
},
|
||||
locale: {
|
||||
controlLabel: "訊息目標語言",
|
||||
label: "目標語言",
|
||||
noTargetLocales: "尚未設定目標語言。",
|
||||
sourceLabel: "來源語言",
|
||||
},
|
||||
messagePanel: {
|
||||
additionalSourceLocations: (count) => `另外 ${count} 個來源位置`,
|
||||
chooseCopyContent: "選擇要複製的內容",
|
||||
collapseNamespace: (path) => `收合 ${path}`,
|
||||
collapsePackage: (packageName) => `收合 ${packageName}`,
|
||||
copied: "已複製",
|
||||
copyDescriptor: "複製 Message Descriptor",
|
||||
copyFailed: "複製失敗",
|
||||
copyMessageId: "複製 Message ID",
|
||||
copySource: "複製原文",
|
||||
copySourceLocation: "複製來源位置",
|
||||
copyTranslation: "複製目前譯文",
|
||||
couldNotSave: "儲存失敗",
|
||||
emptyCatalog: "尚無可用訊息。請先擷取專案訊息以產生目錄。",
|
||||
emptyPackage: "這個套件中暫無訊息。",
|
||||
expandNamespace: (path) => `展開 ${path}`,
|
||||
expandPackage: (packageName) => `展開 ${packageName}`,
|
||||
loading: (locale) => `正在載入 ${locale}…`,
|
||||
messageCount: (count) => `共 ${count} 筆`,
|
||||
messageEditor: "訊息編輯器",
|
||||
messageNavigation: "訊息導覽",
|
||||
missingCount: (count) => `缺漏 ${count} 筆`,
|
||||
missingOnly: "僅顯示缺漏項目",
|
||||
noMatches: "沒有符合目前篩選條件的訊息。",
|
||||
noMissingMessages: "所有訊息皆已完成翻譯。",
|
||||
noSearchResults: "沒有符合搜尋內容的訊息。",
|
||||
reset: "重設",
|
||||
retry: "重試",
|
||||
save: "儲存",
|
||||
saved: "已儲存",
|
||||
saving: "正在儲存…",
|
||||
search: "搜尋訊息",
|
||||
selectMessage: "請選擇一則訊息以檢視和編輯譯文。",
|
||||
source: "原文",
|
||||
sourceLocations: "來源位置",
|
||||
translation: "譯文",
|
||||
translationFor: (id, locale) => `${id} 的 ${locale} 翻譯`,
|
||||
unknownSourceLocation: "未知來源位置",
|
||||
},
|
||||
settings: {
|
||||
auto: "自動",
|
||||
autoTheme: "自動主題",
|
||||
center: "置中",
|
||||
dark: "深色",
|
||||
darkTheme: "深色主題",
|
||||
interfaceLanguage: "介面語言",
|
||||
interfaceLanguageControl: "I18n 開發工具介面語言",
|
||||
label: "I18n 開發工具設定",
|
||||
left: "左側",
|
||||
light: "淺色",
|
||||
lightTheme: "淺色主題",
|
||||
open: "開啟 I18n 開發工具設定",
|
||||
panelPosition: "面板位置",
|
||||
panelPositionControl: "I18n 開發工具面板位置",
|
||||
right: "右側",
|
||||
theme: "主題",
|
||||
themeControl: "I18n 開發工具主題",
|
||||
},
|
||||
trigger: {
|
||||
close: "關閉 I18n 開發工具",
|
||||
open: "開啟 I18n 開發工具",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const defaultContextValue: {
|
||||
locale: I18nDevtoolLocale
|
||||
messages: DevtoolMessages
|
||||
} = {
|
||||
locale: "en",
|
||||
messages: messagesByLocale.en,
|
||||
locale: "en-US",
|
||||
messages: messagesByLocale["en-US"],
|
||||
}
|
||||
|
||||
const DevtoolLocalizationContext = React.createContext(defaultContextValue)
|
||||
@@ -847,30 +295,22 @@ function matchDevtoolLocale(locale: string): I18nDevtoolLocale | undefined {
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedLocale === "zh-hant" ||
|
||||
normalizedLocale.startsWith("zh-hant-")
|
||||
normalizedLocale === "zh" ||
|
||||
normalizedLocale === "zh-hans" ||
|
||||
normalizedLocale.startsWith("zh-hans-") ||
|
||||
normalizedLocale === "zh-cn" ||
|
||||
normalizedLocale.startsWith("zh-cn-") ||
|
||||
normalizedLocale === "zh-sg" ||
|
||||
normalizedLocale.startsWith("zh-sg-")
|
||||
) {
|
||||
return "zh-Hant"
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedLocale === "zh-tw" ||
|
||||
normalizedLocale.startsWith("zh-tw-") ||
|
||||
normalizedLocale === "zh-hk" ||
|
||||
normalizedLocale.startsWith("zh-hk-") ||
|
||||
normalizedLocale === "zh-mo" ||
|
||||
normalizedLocale.startsWith("zh-mo-")
|
||||
) {
|
||||
return "zh-Hant"
|
||||
}
|
||||
|
||||
if (normalizedLocale === "zh" || normalizedLocale.startsWith("zh-")) {
|
||||
return "zh-Hans"
|
||||
}
|
||||
|
||||
const language = normalizedLocale.split("-")[0]
|
||||
if (normalizedLocale === "en" || normalizedLocale.startsWith("en-")) {
|
||||
return "en-US"
|
||||
}
|
||||
|
||||
return I18N_DEVTOOL_LOCALES.find((candidate) => candidate === language)
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getNavigatorLocales() {
|
||||
@@ -899,7 +339,7 @@ export function resolveI18nDevtoolLocale(
|
||||
}
|
||||
}
|
||||
|
||||
return "en"
|
||||
return "en-US"
|
||||
}
|
||||
|
||||
export function getI18nDevtoolMessages(locale: I18nDevtoolLocale) {
|
||||
|
||||
@@ -24,7 +24,7 @@ afterEach(() => {
|
||||
|
||||
function renderDevtool(props?: I18nDevtoolProps) {
|
||||
return render(
|
||||
<I18nProvider catalogs={{ en: {} }} locale="en" locales={["en"]}>
|
||||
<I18nProvider catalogs={{ "en-US": {} }} locale="en-US" locales={["en-US"]}>
|
||||
<I18nDevtool dark="ui:dark" {...props} />
|
||||
</I18nProvider>
|
||||
)
|
||||
@@ -87,17 +87,17 @@ describe("I18nDevtool", () => {
|
||||
it("detects the first supported navigator locale", async () => {
|
||||
vi.spyOn(window.navigator, "languages", "get").mockReturnValue([
|
||||
"pt-BR",
|
||||
"ja-JP",
|
||||
"fr-CA",
|
||||
"en-US",
|
||||
])
|
||||
renderDevtool()
|
||||
|
||||
const trigger = await screen.findByRole("button", {
|
||||
name: "I18n Devtool を開く",
|
||||
name: "Open I18n Devtool",
|
||||
})
|
||||
const devtool = trigger.closest<HTMLElement>('[data-slot="i18n-devtool"]')
|
||||
|
||||
expect(devtool?.getAttribute("lang")).toBe("ja")
|
||||
expect(devtool?.getAttribute("lang")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("falls back to English when an explicit locale is unsupported", async () => {
|
||||
@@ -109,7 +109,7 @@ describe("I18nDevtool", () => {
|
||||
})
|
||||
const devtool = trigger.closest<HTMLElement>('[data-slot="i18n-devtool"]')
|
||||
|
||||
expect(devtool?.getAttribute("lang")).toBe("en")
|
||||
expect(devtool?.getAttribute("lang")).toBe("en-US")
|
||||
})
|
||||
|
||||
it("includes the project source locale as a customization target", async () => {
|
||||
@@ -118,7 +118,7 @@ describe("I18nDevtool", () => {
|
||||
vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input)
|
||||
const body = url.endsWith("/project")
|
||||
? { locales: ["en", "zh-Hans"], sourceLocale: "en" }
|
||||
? { locales: ["en-US", "zh-Hans"], sourceLocale: "en-US" }
|
||||
: []
|
||||
|
||||
return new Response(JSON.stringify(body), {
|
||||
@@ -130,10 +130,10 @@ describe("I18nDevtool", () => {
|
||||
|
||||
render(
|
||||
<I18nProvider
|
||||
catalogs={{ en: {}, "zh-Hans": {} }}
|
||||
locale="en"
|
||||
catalogs={{ "en-US": {}, "zh-Hans": {} }}
|
||||
locale="en-US"
|
||||
locales={[
|
||||
{ label: "English", locale: "en" },
|
||||
{ label: "English", locale: "en-US" },
|
||||
{ label: "简体中文", locale: "zh-Hans" },
|
||||
]}
|
||||
>
|
||||
@@ -146,7 +146,7 @@ describe("I18nDevtool", () => {
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(localeSelect).toHaveProperty("value", "en")
|
||||
expect(localeSelect).toHaveProperty("value", "en-US")
|
||||
})
|
||||
expect(screen.getByText("Source locale")).toBeTruthy()
|
||||
expect(screen.getByText("English", { selector: "output" })).toBeTruthy()
|
||||
@@ -157,9 +157,9 @@ describe("I18nDevtool", () => {
|
||||
it("tracks the configured document dark-mode class", async () => {
|
||||
render(
|
||||
<I18nProvider
|
||||
catalogs={{ en: {} }}
|
||||
locale="en"
|
||||
locales={["en", "zh-Hans"]}
|
||||
catalogs={{ "en-US": {} }}
|
||||
locale="en-US"
|
||||
locales={["en-US", "zh-Hans"]}
|
||||
>
|
||||
<I18nDevtool dark={"ui\\:dark"} />
|
||||
</I18nProvider>
|
||||
|
||||
@@ -24,7 +24,7 @@ const message: DevtoolMessage = {
|
||||
origins: [{ file: "src/navigation.tsx", line: 12 }],
|
||||
packageName: "@workspace/ui",
|
||||
source: "Home",
|
||||
sourceLocale: "en",
|
||||
sourceLocale: "en-US",
|
||||
translation: "",
|
||||
}
|
||||
|
||||
@@ -310,10 +310,10 @@ describe("MessagePanel", () => {
|
||||
updateMessage,
|
||||
}
|
||||
|
||||
render(<MessagePanel locale="en" repository={repository} />)
|
||||
render(<MessagePanel locale="en-US" repository={repository} />)
|
||||
|
||||
const textarea = await screen.findByLabelText(
|
||||
"navigation.home translation for en"
|
||||
"navigation.home translation for en-US"
|
||||
)
|
||||
|
||||
expect(textarea).toHaveProperty("readOnly", true)
|
||||
|
||||
Reference in New Issue
Block a user