feat(i18n): 统一国际化系统架构,支持插件独立翻译 (#301)

* feat(i18n): 统一国际化系统架构,支持插件独立翻译

## 主要改动

### 核心架构
- 增强 LocaleService,支持插件命名空间翻译扩展
- 新增 editor-runtime/i18n 模块,提供 createPluginLocale/createPluginTranslator
- 新增 editor-core/tokens.ts,定义 LocaleServiceToken 等服务令牌
- 改进 PluginAPI 类型安全,使用 ServiceToken<T> 替代 any

### 编辑器本地化
- 扩展 en.ts/zh.ts 翻译文件,覆盖所有 UI 组件
- 新增 es.ts 西班牙语支持
- 重构 40+ 组件使用 useLocale() hook

### 插件本地化系统
- behavior-tree-editor: 新增 locales/ 和 useBTLocale hook
- material-editor: 新增 locales/ 和 useMaterialLocale hook
- particle-editor: 新增 locales/ 和 useParticleLocale hook
- tilemap-editor: 新增 locales/ 和 useTilemapLocale hook
- ui-editor: 新增 locales/ 和 useUILocale hook

### 类型安全改进
- 修复 Debug 工具使用公共接口替代 as any
- 修复 ChunkStreamingSystem 添加 forEachChunk 公共方法
- 修复 blueprint-editor 移除不必要的向后兼容代码

* fix(behavior-tree-editor): 使用 ServiceToken 模式修复服务解析

- 创建 BehaviorTreeServiceToken 遵循"谁定义接口,谁导出Token"原则
- 使用 ServiceToken.id (symbol) 注册服务到 ServiceContainer
- 更新 PluginSDKRegistry.resolveService 支持 ServiceToken 检测
- BehaviorTreeEditorPanel 现在使用类型安全的 PluginAPI.resolve

* fix(behavior-tree-editor): 使用 ServiceContainer.resolve 获取类注册的服务

* fix: 修复多个包的依赖和类型问题

- core: EntityDataCollector.getEntityDetails 使用 HierarchySystem 获取父实体
- ui-editor: 添加 @esengine/editor-runtime 依赖
- tilemap-editor: 添加 @esengine/editor-runtime 依赖
- particle-editor: 添加 @esengine/editor-runtime 依赖
This commit is contained in:
YHH
2025-12-09 18:04:03 +08:00
committed by GitHub
parent 995fa2d514
commit 1b0d38edce
103 changed files with 8015 additions and 1633 deletions

View File

@@ -31,6 +31,7 @@
"@esengine/ecs-framework": "workspace:*",
"@esengine/engine-core": "workspace:*",
"@esengine/editor-core": "workspace:*",
"@esengine/editor-runtime": "workspace:*",
"@esengine/build-config": "workspace:*",
"lucide-react": "^0.545.0",
"react": "^18.3.1",

View File

@@ -0,0 +1,70 @@
/**
* Particle Editor Locale Hook
* 粒子编辑器语言钩子
*
* Uses the unified plugin i18n infrastructure from editor-runtime.
* 使用 editor-runtime 的统一插件国际化基础设施。
*/
import {
createPluginLocale,
createPluginTranslator,
getCurrentLocale
} from '@esengine/editor-runtime';
import { en, zh, es } from '../locales';
import type { Locale, TranslationParams } from '@esengine/editor-core';
// Create translations bundle
// 创建翻译包
const translations = { en, zh, es };
/**
* Hook for accessing particle editor translations
* 访问粒子编辑器翻译的 Hook
*
* Uses the unified createPluginLocale factory from editor-runtime.
* 使用 editor-runtime 的统一 createPluginLocale 工厂。
*
* @example
* ```tsx
* const { t, locale } = useParticleLocale();
* return <PropertyInput label={t('basic.name')} />;
* ```
*/
export const useParticleLocale = createPluginLocale(translations);
// Create non-React translator using the unified infrastructure
// 使用统一基础设施创建非 React 翻译器
const particleTranslator = createPluginTranslator(translations);
/**
* Non-React translation function for particle editor
* 粒子编辑器的非 React 翻译函数
*
* Use this in services, utilities, and other non-React contexts.
* 在服务、工具类和其他非 React 上下文中使用。
*
* @param key - Translation key | 翻译键
* @param locale - Optional locale, defaults to current locale | 可选语言,默认使用当前语言
* @param params - Optional interpolation parameters | 可选插值参数
*
* @example
* ```typescript
* // With explicit locale
* translateParticle('notifications.fileSaved', 'zh', { path: '/path/to/file' });
*
* // With current locale (auto-detected)
* translateParticle('basic.name');
* ```
*/
export function translateParticle(
key: string,
locale?: Locale,
params?: TranslationParams
): string {
const targetLocale = locale || getCurrentLocale();
return particleTranslator(key, targetLocale, params);
}
// Re-export for external use
// 重新导出供外部使用
export { getCurrentLocale } from '@esengine/editor-runtime';

View File

@@ -0,0 +1,267 @@
/**
* English translations for Particle Editor
* 粒子编辑器英文翻译
*/
export const en = {
// ========================================
// Panel
// ========================================
panel: {
title: 'Particle Editor',
noFileOpen: 'No particle file is open',
dropToOpen: 'Drop a .particle file here or use Open button'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
play: 'Play',
pause: 'Pause',
restart: 'Restart',
save: 'Save',
open: 'Open',
maximize: 'Maximize preview',
minimize: 'Minimize preview',
followMouse: 'Follow mouse',
resetPosition: 'Reset position'
},
// ========================================
// Sections
// ========================================
sections: {
basic: 'Basic',
emission: 'Emission',
particle: 'Particle',
color: 'Color',
modules: 'Modules',
presets: 'Presets'
},
// ========================================
// Basic Properties
// ========================================
basic: {
name: 'Name',
texture: 'Texture',
maxParticles: 'Max Particles',
looping: 'Looping',
duration: 'Duration',
prewarm: 'Prewarm',
playSpeed: 'Play Speed',
blendMode: 'Blend Mode',
space: 'Space',
particleSize: 'Particle Size',
sortOrder: 'Sort Order'
},
// ========================================
// Blend Modes
// ========================================
blendMode: {
normal: 'Normal',
additive: 'Additive',
multiply: 'Multiply'
},
// ========================================
// Simulation Space
// ========================================
space: {
world: 'World',
local: 'Local'
},
// ========================================
// Emission Properties
// ========================================
emission: {
rate: 'Rate',
shape: 'Shape',
radius: 'Radius',
width: 'Width',
height: 'Height',
coneAngle: 'Cone Angle'
},
// ========================================
// Emission Shapes
// ========================================
shapes: {
point: 'Point',
circle: 'Circle',
ring: 'Ring',
rectangle: 'Rectangle',
edge: 'Edge',
line: 'Line',
cone: 'Cone'
},
// ========================================
// Particle Properties
// ========================================
particle: {
lifetime: 'Lifetime',
speed: 'Speed',
direction: 'Direction',
spread: 'Spread',
scale: 'Scale',
gravity: 'Gravity'
},
// ========================================
// Color Properties
// ========================================
color: {
startColor: 'Start Color',
startAlpha: 'Start Alpha',
endAlpha: 'End Alpha',
endScale: 'End Scale'
},
// ========================================
// Module Names
// ========================================
modules: {
colorOverLifetime: 'Color Over Lifetime',
sizeOverLifetime: 'Size Over Lifetime',
velocityOverLifetime: 'Velocity Over Lifetime',
rotationOverLifetime: 'Rotation Over Lifetime',
noise: 'Noise',
collision: 'Collision',
forceField: 'Force Field'
},
// ========================================
// Velocity Over Lifetime
// ========================================
velocity: {
drag: 'Drag',
orbital: 'Orbital',
radial: 'Radial'
},
// ========================================
// Rotation Over Lifetime
// ========================================
rotation: {
startMult: 'Start Mult',
endMult: 'End Mult',
additional: 'Additional'
},
// ========================================
// Noise Module
// ========================================
noise: {
position: 'Position',
velocity: 'Velocity',
rotation: 'Rotation',
frequency: 'Frequency',
scroll: 'Scroll'
},
// ========================================
// Collision Module
// ========================================
collision: {
boundary: 'Boundary',
behavior: 'Behavior',
left: 'Left',
right: 'Right',
top: 'Top',
bottom: 'Bottom',
radius: 'Radius',
bounce: 'Bounce',
lifeLoss: 'Life Loss'
},
// ========================================
// Boundary Types
// ========================================
boundaryType: {
none: 'None',
rectangle: 'Rectangle',
circle: 'Circle'
},
// ========================================
// Collision Behaviors
// ========================================
collisionBehavior: {
kill: 'Kill',
bounce: 'Bounce',
wrap: 'Wrap'
},
// ========================================
// Force Field Module
// ========================================
forceField: {
type: 'Type',
strength: 'Strength',
directionX: 'Direction X',
directionY: 'Direction Y',
centerX: 'Center X',
centerY: 'Center Y',
range: 'Range',
falloff: 'Falloff'
},
// ========================================
// Force Field Types
// ========================================
forceFieldType: {
wind: 'Wind',
point: 'Point',
vortex: 'Vortex',
turbulence: 'Turbulence'
},
// ========================================
// Curve Editor
// ========================================
curve: {
deletePoint: 'Delete point',
constant: 'Constant value',
fadeIn: 'Fade in',
fadeOut: 'Fade out',
bellCurve: 'Bell curve',
uCurve: 'U curve'
},
// ========================================
// Gradient Editor
// ========================================
gradient: {
deleteStop: 'Delete stop'
},
// ========================================
// Texture Picker
// ========================================
texturePicker: {
browse: 'Browse...',
clear: 'Clear'
},
// ========================================
// Notifications
// ========================================
notifications: {
fileSaved: 'File saved: {{path}}',
fileSaveFailed: 'Failed to save file',
fileOpened: 'File opened: {{path}}',
fileOpenFailed: 'Failed to open file'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTexture: 'Select texture image',
selectParticleFile: 'Select particle file',
saveParticleFile: 'Save particle file'
}
};

View File

@@ -0,0 +1,267 @@
/**
* Spanish translations for Particle Editor
* Traducciones en español del editor de partículas
*/
export const es = {
// ========================================
// Panel
// ========================================
panel: {
title: 'Editor de Partículas',
noFileOpen: 'No hay archivo de partículas abierto',
dropToOpen: 'Arrastre un archivo .particle aquí o use el botón Abrir'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
play: 'Reproducir',
pause: 'Pausar',
restart: 'Reiniciar',
save: 'Guardar',
open: 'Abrir',
maximize: 'Maximizar vista previa',
minimize: 'Minimizar vista previa',
followMouse: 'Seguir ratón',
resetPosition: 'Restablecer posición'
},
// ========================================
// Sections
// ========================================
sections: {
basic: 'Básico',
emission: 'Emisión',
particle: 'Partícula',
color: 'Color',
modules: 'Módulos',
presets: 'Preajustes'
},
// ========================================
// Basic Properties
// ========================================
basic: {
name: 'Nombre',
texture: 'Textura',
maxParticles: 'Máx. Partículas',
looping: 'Bucle',
duration: 'Duración',
prewarm: 'Precalentamiento',
playSpeed: 'Velocidad',
blendMode: 'Modo mezcla',
space: 'Espacio',
particleSize: 'Tamaño partícula',
sortOrder: 'Orden'
},
// ========================================
// Blend Modes
// ========================================
blendMode: {
normal: 'Normal',
additive: 'Aditivo',
multiply: 'Multiplicar'
},
// ========================================
// Simulation Space
// ========================================
space: {
world: 'Mundo',
local: 'Local'
},
// ========================================
// Emission Properties
// ========================================
emission: {
rate: 'Tasa',
shape: 'Forma',
radius: 'Radio',
width: 'Ancho',
height: 'Alto',
coneAngle: 'Ángulo cono'
},
// ========================================
// Emission Shapes
// ========================================
shapes: {
point: 'Punto',
circle: 'Círculo',
ring: 'Anillo',
rectangle: 'Rectángulo',
edge: 'Borde',
line: 'Línea',
cone: 'Cono'
},
// ========================================
// Particle Properties
// ========================================
particle: {
lifetime: 'Vida',
speed: 'Velocidad',
direction: 'Dirección',
spread: 'Dispersión',
scale: 'Escala',
gravity: 'Gravedad'
},
// ========================================
// Color Properties
// ========================================
color: {
startColor: 'Color inicial',
startAlpha: 'Alfa inicial',
endAlpha: 'Alfa final',
endScale: 'Escala final'
},
// ========================================
// Module Names
// ========================================
modules: {
colorOverLifetime: 'Color durante vida',
sizeOverLifetime: 'Tamaño durante vida',
velocityOverLifetime: 'Velocidad durante vida',
rotationOverLifetime: 'Rotación durante vida',
noise: 'Ruido',
collision: 'Colisión',
forceField: 'Campo de fuerza'
},
// ========================================
// Velocity Over Lifetime
// ========================================
velocity: {
drag: 'Arrastre',
orbital: 'Orbital',
radial: 'Radial'
},
// ========================================
// Rotation Over Lifetime
// ========================================
rotation: {
startMult: 'Mult. inicial',
endMult: 'Mult. final',
additional: 'Adicional'
},
// ========================================
// Noise Module
// ========================================
noise: {
position: 'Posición',
velocity: 'Velocidad',
rotation: 'Rotación',
frequency: 'Frecuencia',
scroll: 'Desplazamiento'
},
// ========================================
// Collision Module
// ========================================
collision: {
boundary: 'Límite',
behavior: 'Comportamiento',
left: 'Izquierda',
right: 'Derecha',
top: 'Arriba',
bottom: 'Abajo',
radius: 'Radio',
bounce: 'Rebote',
lifeLoss: 'Pérdida de vida'
},
// ========================================
// Boundary Types
// ========================================
boundaryType: {
none: 'Ninguno',
rectangle: 'Rectángulo',
circle: 'Círculo'
},
// ========================================
// Collision Behaviors
// ========================================
collisionBehavior: {
kill: 'Eliminar',
bounce: 'Rebotar',
wrap: 'Envolver'
},
// ========================================
// Force Field Module
// ========================================
forceField: {
type: 'Tipo',
strength: 'Fuerza',
directionX: 'Dirección X',
directionY: 'Dirección Y',
centerX: 'Centro X',
centerY: 'Centro Y',
range: 'Rango',
falloff: 'Caída'
},
// ========================================
// Force Field Types
// ========================================
forceFieldType: {
wind: 'Viento',
point: 'Punto',
vortex: 'Vórtice',
turbulence: 'Turbulencia'
},
// ========================================
// Curve Editor
// ========================================
curve: {
deletePoint: 'Eliminar punto',
constant: 'Valor constante',
fadeIn: 'Aparecer',
fadeOut: 'Desvanecer',
bellCurve: 'Curva campana',
uCurve: 'Curva U'
},
// ========================================
// Gradient Editor
// ========================================
gradient: {
deleteStop: 'Eliminar parada'
},
// ========================================
// Texture Picker
// ========================================
texturePicker: {
browse: 'Examinar...',
clear: 'Limpiar'
},
// ========================================
// Notifications
// ========================================
notifications: {
fileSaved: 'Archivo guardado: {{path}}',
fileSaveFailed: 'Error al guardar archivo',
fileOpened: 'Archivo abierto: {{path}}',
fileOpenFailed: 'Error al abrir archivo'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTexture: 'Seleccionar imagen de textura',
selectParticleFile: 'Seleccionar archivo de partículas',
saveParticleFile: 'Guardar archivo de partículas'
}
};

View File

@@ -0,0 +1,11 @@
/**
* Particle Editor Locales
* 粒子编辑器语言包
*
* Export all locale translations for the particle editor plugin.
* 导出粒子编辑器插件的所有语言翻译。
*/
export { en } from './en';
export { zh } from './zh';
export { es } from './es';

View File

@@ -0,0 +1,267 @@
/**
* Chinese translations for Particle Editor
* 粒子编辑器中文翻译
*/
export const zh = {
// ========================================
// Panel
// ========================================
panel: {
title: '粒子编辑器',
noFileOpen: '没有打开的粒子文件',
dropToOpen: '拖放 .particle 文件到这里或使用打开按钮'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
play: '播放',
pause: '暂停',
restart: '重新开始',
save: '保存',
open: '打开',
maximize: '最大化预览',
minimize: '最小化预览',
followMouse: '跟随鼠标',
resetPosition: '重置位置'
},
// ========================================
// Sections
// ========================================
sections: {
basic: '基础',
emission: '发射',
particle: '粒子',
color: '颜色',
modules: '模块',
presets: '预设'
},
// ========================================
// Basic Properties
// ========================================
basic: {
name: '名称',
texture: '纹理',
maxParticles: '最大粒子数',
looping: '循环',
duration: '持续时间',
prewarm: '预热',
playSpeed: '播放速度',
blendMode: '混合模式',
space: '空间',
particleSize: '粒子大小',
sortOrder: '排序顺序'
},
// ========================================
// Blend Modes
// ========================================
blendMode: {
normal: '普通',
additive: '叠加',
multiply: '乘法'
},
// ========================================
// Simulation Space
// ========================================
space: {
world: '世界',
local: '本地'
},
// ========================================
// Emission Properties
// ========================================
emission: {
rate: '发射率',
shape: '形状',
radius: '半径',
width: '宽度',
height: '高度',
coneAngle: '锥形角度'
},
// ========================================
// Emission Shapes
// ========================================
shapes: {
point: '点',
circle: '圆形',
ring: '环形',
rectangle: '矩形',
edge: '边缘',
line: '线',
cone: '锥形'
},
// ========================================
// Particle Properties
// ========================================
particle: {
lifetime: '生命周期',
speed: '速度',
direction: '方向',
spread: '散布',
scale: '缩放',
gravity: '重力'
},
// ========================================
// Color Properties
// ========================================
color: {
startColor: '起始颜色',
startAlpha: '起始透明度',
endAlpha: '结束透明度',
endScale: '结束缩放'
},
// ========================================
// Module Names
// ========================================
modules: {
colorOverLifetime: '颜色随生命周期',
sizeOverLifetime: '大小随生命周期',
velocityOverLifetime: '速度随生命周期',
rotationOverLifetime: '旋转随生命周期',
noise: '噪声',
collision: '碰撞',
forceField: '力场'
},
// ========================================
// Velocity Over Lifetime
// ========================================
velocity: {
drag: '阻力',
orbital: '轨道',
radial: '径向'
},
// ========================================
// Rotation Over Lifetime
// ========================================
rotation: {
startMult: '起始倍数',
endMult: '结束倍数',
additional: '附加'
},
// ========================================
// Noise Module
// ========================================
noise: {
position: '位置',
velocity: '速度',
rotation: '旋转',
frequency: '频率',
scroll: '滚动'
},
// ========================================
// Collision Module
// ========================================
collision: {
boundary: '边界',
behavior: '行为',
left: '左',
right: '右',
top: '上',
bottom: '下',
radius: '半径',
bounce: '弹跳',
lifeLoss: '生命损失'
},
// ========================================
// Boundary Types
// ========================================
boundaryType: {
none: '无',
rectangle: '矩形',
circle: '圆形'
},
// ========================================
// Collision Behaviors
// ========================================
collisionBehavior: {
kill: '消灭',
bounce: '弹跳',
wrap: '环绕'
},
// ========================================
// Force Field Module
// ========================================
forceField: {
type: '类型',
strength: '强度',
directionX: '方向 X',
directionY: '方向 Y',
centerX: '中心 X',
centerY: '中心 Y',
range: '范围',
falloff: '衰减'
},
// ========================================
// Force Field Types
// ========================================
forceFieldType: {
wind: '风',
point: '点',
vortex: '漩涡',
turbulence: '湍流'
},
// ========================================
// Curve Editor
// ========================================
curve: {
deletePoint: '删除点',
constant: '常量',
fadeIn: '淡入',
fadeOut: '淡出',
bellCurve: '钟形曲线',
uCurve: 'U 形曲线'
},
// ========================================
// Gradient Editor
// ========================================
gradient: {
deleteStop: '删除色标'
},
// ========================================
// Texture Picker
// ========================================
texturePicker: {
browse: '浏览...',
clear: '清除'
},
// ========================================
// Notifications
// ========================================
notifications: {
fileSaved: '文件已保存: {{path}}',
fileSaveFailed: '保存文件失败',
fileOpened: '文件已打开: {{path}}',
fileOpenFailed: '打开文件失败'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTexture: '选择纹理图片',
selectParticleFile: '选择粒子文件',
saveParticleFile: '保存粒子文件'
}
};