多语言调整
This commit is contained in:
1 parent
04de5e5a8e
commit
8bd9154414
23 files changed
+395
-155
No files matched your search
@@ -38,7 +38,6 @@ const overrides = {
|
||||
export: { title: '匯出設定', spineVersion: 'Spine 版本', fps: '匯出 FPS', exportJson: '匯出 Spine JSON', exportImages: '匯出圖片 ZIP', connectorReady: '連接器可用 · Spine 已就緒', connectorOffline: '本機 Spine 連接器未啟動', spineNotFound: '連接器已啟動,但找不到 Spine', notAuthorized: '連接器已啟動,但目前網站尚未授權', checking: '偵測中', offline: '未啟動', notFound: '找不到 Spine', unauthorized: '未授權', connectorBusy: '正在重新計算並產生 Spine 專案', connectorGenerate: '使用本機 Spine 自動產生 {version} 專案' },
|
||||
spine: { title: 'Spine 屬性', resources: 'Spine 資源', animations: 'Spine 動畫', hierarchy: '階層樹', xPosition: 'X 位置', yPosition: 'Y 位置', rotation: '旋轉', startTime: '開始時間', scaleX: 'X 縮放', scaleY: 'Y 縮放' },
|
||||
help: { title: '使用說明', commonOps: '常用操作', shortcuts: '快速鍵', modules: '功能模組詳解' },
|
||||
presets: { particleSweep: '1.粒子掃過', barBlocks: '2.長條方塊', forceTrail: '3.力場拖尾', lightningSequence: '4.雷電序列幀', texturePivot: '5.貼圖軸點', subEmitter: '6.子發射器' },
|
||||
},
|
||||
ja: {
|
||||
common: {
|
||||
@@ -77,7 +76,6 @@ const overrides = {
|
||||
export: { title: 'エクスポート設定', spineVersion: 'Spine バージョン', fps: 'エクスポート FPS', exportJson: 'Spine JSON を書き出す', exportImages: '画像 ZIP を書き出す', connectorReady: 'コネクタ使用可能 · Spine 準備完了', connectorOffline: 'ローカル Spine コネクタが起動していません', spineNotFound: 'コネクタは起動していますが Spine が見つかりません', notAuthorized: 'コネクタは起動していますが、このサイトは未認証です', checking: '確認中', offline: '未起動', notFound: 'Spine が見つかりません', unauthorized: '未認証', connectorBusy: 'Spine プロジェクトを再計算して生成中', connectorGenerate: 'ローカル Spine で Spine {version} プロジェクトを生成' },
|
||||
spine: { title: 'Spine プロパティ', resources: 'Spine リソース', animations: 'Spine アニメーション', hierarchy: '階層ツリー', xPosition: 'X 位置', yPosition: 'Y 位置', rotation: '回転', startTime: '開始時間', scaleX: 'X スケール', scaleY: 'Y スケール' },
|
||||
help: { title: 'ヘルプ', commonOps: 'よく使う操作', shortcuts: 'ショートカット', modules: '機能モジュール' },
|
||||
presets: { particleSweep: '1.パーティクルスイープ', barBlocks: '2.バーブロック', forceTrail: '3.フォースフィールドトレイル', lightningSequence: '4.雷シーケンス', texturePivot: '5.テクスチャピボット', subEmitter: '6.サブエミッター' },
|
||||
},
|
||||
ko: {
|
||||
common: {
|
||||
@@ -116,7 +114,6 @@ const overrides = {
|
||||
export: { title: '내보내기 설정', spineVersion: 'Spine 버전', fps: '내보내기 FPS', exportJson: 'Spine JSON 내보내기', exportImages: '이미지 ZIP 내보내기', connectorReady: '커넥터 사용 가능 · Spine 준비됨', connectorOffline: '로컬 Spine 커넥터가 실행 중이 아닙니다', spineNotFound: '커넥터는 실행 중이지만 Spine을 찾을 수 없습니다', notAuthorized: '커넥터는 실행 중이지만 이 사이트는 승인되지 않았습니다', checking: '확인 중', offline: '오프라인', notFound: 'Spine 없음', unauthorized: '승인되지 않음', connectorBusy: 'Spine 프로젝트를 다시 계산하고 생성 중', connectorGenerate: '로컬 Spine으로 Spine {version} 프로젝트 생성' },
|
||||
spine: { title: 'Spine 속성', resources: 'Spine 리소스', animations: 'Spine 애니메이션', hierarchy: '계층 트리', xPosition: 'X 위치', yPosition: 'Y 위치', rotation: '회전', startTime: '시작 시간', scaleX: 'X 크기', scaleY: 'Y 크기' },
|
||||
help: { title: '도움말', commonOps: '자주 쓰는 작업', shortcuts: '단축키', modules: '기능 모듈' },
|
||||
presets: { particleSweep: '1.파티클 스윕', barBlocks: '2.바 블록', forceTrail: '3.힘장 트레일', lightningSequence: '4.번개 시퀀스', texturePivot: '5.텍스처 피벗', subEmitter: '6.하위 이미터' },
|
||||
},
|
||||
fr: {
|
||||
common: {
|
||||
@@ -155,7 +152,6 @@ const overrides = {
|
||||
export: { title: 'Paramètres d’export', spineVersion: 'Version de Spine', fps: 'FPS d’export', exportJson: 'Exporter Spine JSON', exportImages: 'Exporter les images ZIP', connectorReady: 'Connecteur disponible · Spine prêt', connectorOffline: 'Le connecteur Spine local n’est pas démarré', spineNotFound: 'Le connecteur est démarré, mais Spine est introuvable', notAuthorized: 'Le connecteur est démarré, mais ce site n’est pas autorisé', checking: 'Vérification', offline: 'Hors ligne', notFound: 'Spine introuvable', unauthorized: 'Non autorisé', connectorBusy: 'Recalcul et génération du projet Spine', connectorGenerate: 'Générer un projet Spine {version} avec Spine local' },
|
||||
spine: { title: 'Propriétés Spine', resources: 'Ressources Spine', animations: 'Animations Spine', hierarchy: 'Hiérarchie', xPosition: 'Position X', yPosition: 'Position Y', rotation: 'Rotation', startTime: 'Heure de début', scaleX: 'Échelle X', scaleY: 'Échelle Y' },
|
||||
help: { title: 'Aide', commonOps: 'Actions courantes', shortcuts: 'Raccourcis', modules: 'Guide des modules' },
|
||||
presets: { particleSweep: '1.Balayage de particules', barBlocks: '2.Blocs rectangulaires', forceTrail: '3.Traînée de champ de force', lightningSequence: '4.Séquence d’éclairs', texturePivot: '5.Pivot de texture', subEmitter: '6.Sous-émetteur' },
|
||||
},
|
||||
de: {
|
||||
common: {
|
||||
@@ -194,7 +190,6 @@ const overrides = {
|
||||
export: { title: 'Exporteinstellungen', spineVersion: 'Spine-Version', fps: 'Export-FPS', exportJson: 'Spine JSON exportieren', exportImages: 'Bilder-ZIP exportieren', connectorReady: 'Connector verfügbar · Spine bereit', connectorOffline: 'Der lokale Spine-Connector läuft nicht', spineNotFound: 'Der Connector läuft, aber Spine wurde nicht gefunden', notAuthorized: 'Der Connector läuft, aber diese Website ist nicht autorisiert', checking: 'Prüfung', offline: 'Offline', notFound: 'Spine nicht gefunden', unauthorized: 'Nicht autorisiert', connectorBusy: 'Spine-Projekt wird neu berechnet und erstellt', connectorGenerate: 'Spine-{version}-Projekt mit lokalem Spine erstellen' },
|
||||
spine: { title: 'Spine-Eigenschaften', resources: 'Spine-Ressourcen', animations: 'Spine-Animationen', hierarchy: 'Hierarchie', xPosition: 'X-Position', yPosition: 'Y-Position', rotation: 'Drehung', startTime: 'Startzeit', scaleX: 'X-Skalierung', scaleY: 'Y-Skalierung' },
|
||||
help: { title: 'Hilfe', commonOps: 'Häufige Aktionen', shortcuts: 'Tastenkürzel', modules: 'Modulhandbuch' },
|
||||
presets: { particleSweep: '1.Partikel-Sweep', barBlocks: '2.Balkenblöcke', forceTrail: '3.Kraftfeldschweif', lightningSequence: '4.Blitzsequenz', texturePivot: '5.Textur-Pivot', subEmitter: '6.Sub-Emitter' },
|
||||
},
|
||||
'pt-BR': {
|
||||
common: {
|
||||
@@ -233,7 +228,6 @@ const overrides = {
|
||||
export: { title: 'Configurações de exportação', spineVersion: 'Versão do Spine', fps: 'FPS de exportação', exportJson: 'Exportar Spine JSON', exportImages: 'Exportar imagens ZIP', connectorReady: 'Conector disponível · Spine pronto', connectorOffline: 'O conector local do Spine não está em execução', spineNotFound: 'O conector está ativo, mas o Spine não foi encontrado', notAuthorized: 'O conector está ativo, mas este site não está autorizado', checking: 'Verificando', offline: 'Offline', notFound: 'Spine não encontrado', unauthorized: 'Não autorizado', connectorBusy: 'Recalculando e gerando o projeto Spine', connectorGenerate: 'Gerar projeto Spine {version} com o Spine local' },
|
||||
spine: { title: 'Propriedades do Spine', resources: 'Recursos Spine', animations: 'Animações Spine', hierarchy: 'Hierarquia', xPosition: 'Posição X', yPosition: 'Posição Y', rotation: 'Rotação', startTime: 'Tempo inicial', scaleX: 'Escala X', scaleY: 'Escala Y' },
|
||||
help: { title: 'Ajuda', commonOps: 'Ações comuns', shortcuts: 'Atalhos', modules: 'Guia dos módulos' },
|
||||
presets: { particleSweep: '1.Varredura de partículas', barBlocks: '2.Blocos retangulares', forceTrail: '3.Rastro de campo de força', lightningSequence: '4.Sequência de raios', texturePivot: '5.Pivô da textura', subEmitter: '6.Subemissor' },
|
||||
},
|
||||
es: {
|
||||
common: {
|
||||
@@ -272,7 +266,6 @@ const overrides = {
|
||||
export: { title: 'Ajustes de exportación', spineVersion: 'Versión de Spine', fps: 'FPS de exportación', exportJson: 'Exportar Spine JSON', exportImages: 'Exportar imágenes ZIP', connectorReady: 'Conector disponible · Spine listo', connectorOffline: 'El conector local de Spine no está iniciado', spineNotFound: 'El conector está iniciado, pero no se encontró Spine', notAuthorized: 'El conector está iniciado, pero este sitio no está autorizado', checking: 'Comprobando', offline: 'Sin conexión', notFound: 'Spine no encontrado', unauthorized: 'No autorizado', connectorBusy: 'Recalculando y generando el proyecto de Spine', connectorGenerate: 'Generar proyecto Spine {version} con Spine local' },
|
||||
spine: { title: 'Propiedades de Spine', resources: 'Recursos de Spine', animations: 'Animaciones de Spine', hierarchy: 'Jerarquía', xPosition: 'Posición X', yPosition: 'Posición Y', rotation: 'Rotación', startTime: 'Tiempo inicial', scaleX: 'Escala X', scaleY: 'Escala Y' },
|
||||
help: { title: 'Ayuda', commonOps: 'Acciones comunes', shortcuts: 'Atajos', modules: 'Guía de módulos' },
|
||||
presets: { particleSweep: '1.Barrido de partículas', barBlocks: '2.Bloques rectangulares', forceTrail: '3.Estela de campo de fuerza', lightningSequence: '4.Secuencia de rayos', texturePivot: '5.Pivote de textura', subEmitter: '6.Subemisor' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+51
-2
@@ -1,4 +1,4 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { build } from 'esbuild'
|
||||
|
||||
const localeCodes = ['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'pt-BR', 'es']
|
||||
@@ -7,6 +7,7 @@ const localeMessages = Object.fromEntries(await Promise.all(localeCodes.map(asyn
|
||||
JSON.parse(await readFile(`src/i18n/locales/${code}.json`, 'utf8')),
|
||||
])))
|
||||
const zh = localeMessages['zh-CN']
|
||||
const presetIndex = JSON.parse(await readFile('public/Preset/index.json', 'utf8'))
|
||||
|
||||
function flattenKeys(value, prefix = '') {
|
||||
const keys = []
|
||||
@@ -59,6 +60,22 @@ for (const code of localeCodes.slice(1)) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const preset of presetIndex) {
|
||||
const file = typeof preset === 'string' ? preset : preset?.file
|
||||
if (typeof file !== 'string' || !file.trim()) throw new Error('Preset index entry is missing a file')
|
||||
await stat(`public/Preset/${file}`)
|
||||
if (typeof preset === 'string' || preset.name == null) continue
|
||||
if (typeof preset.name === 'string') continue
|
||||
if (typeof preset.name !== 'object' || Array.isArray(preset.name)) {
|
||||
throw new Error(`Preset ${file} has an invalid name`)
|
||||
}
|
||||
for (const [code, name] of Object.entries(preset.name)) {
|
||||
if (!localeCodes.includes(code) || typeof name !== 'string' || !name.trim()) {
|
||||
throw new Error(`Preset ${file} has an invalid ${code} display name`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bundled = await build({
|
||||
entryPoints: ['src/i18n/index.ts'],
|
||||
bundle: true,
|
||||
@@ -71,6 +88,38 @@ const i18nModule = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(bundled.outputFiles[0].contents).toString('base64')}`
|
||||
)
|
||||
|
||||
const presetNamesBundle = await build({
|
||||
entryPoints: ['src/views/particle-panel/presetNames.ts'],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
write: false,
|
||||
logLevel: 'silent',
|
||||
})
|
||||
const presetNames = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(presetNamesBundle.outputFiles[0].contents).toString('base64')}`
|
||||
)
|
||||
const parsedPresets = presetNames.parsePresetIndex([
|
||||
{ file: '8.test.json' },
|
||||
'9.raw.json',
|
||||
{ file: 'old.json', name: 'Old Preset' },
|
||||
{ file: 'partial.json', name: { ja: '日本語プリセット' } },
|
||||
])
|
||||
if (parsedPresets.length !== 4) throw new Error('Preset compatibility parser rejected legacy entries')
|
||||
const presetCases = [
|
||||
[parsedPresets[0], 'zh-CN', '8.test'],
|
||||
[parsedPresets[1], 'en', '9.raw'],
|
||||
[parsedPresets[2], 'fr', 'Old Preset'],
|
||||
[parsedPresets[3], 'ja', '日本語プリセット'],
|
||||
[parsedPresets[3], 'fr', 'partial'],
|
||||
]
|
||||
for (const [preset, locale, expected] of presetCases) {
|
||||
const actual = presetNames.resolvePresetDisplayName(preset, locale)
|
||||
if (actual !== expected) {
|
||||
throw new Error(`Preset fallback failed: expected ${expected}, got ${actual}`)
|
||||
}
|
||||
}
|
||||
|
||||
const cases = [
|
||||
[null, ['zh-CN'], 'zh-CN'],
|
||||
[null, ['zh-Hant-TW'], 'zh-TW'],
|
||||
@@ -95,4 +144,4 @@ for (const [saved, browserLanguages, expected] of cases) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`i18n 校验通过:${localeCodes.length} 种语言、${zhKeys.length} 个消息键,键与占位符一致,自动语言与缓存优先级正确`)
|
||||
console.log(`i18n 校验通过:${localeCodes.length} 种语言、${zhKeys.length} 个消息键,预设索引兼容,自动语言与缓存优先级正确`)
|
||||
Reference in new issue
Block a user