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,73 @@
/**
* Tilemap 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 tilemap editor translations
* 访问瓦片地图编辑器翻译的 Hook
*
* Uses the unified createPluginLocale factory from editor-runtime.
* 使用 editor-runtime 的统一 createPluginLocale 工厂。
*
* @example
* ```tsx
* const { t, locale } = useTilemapLocale();
* return <button title={t('toolbar.save')}>{t('toolbar.saveButton')}</button>;
* ```
*/
export const useTilemapLocale = createPluginLocale(translations);
// Create non-React translator using the unified infrastructure
// 使用统一基础设施创建非 React 翻译器
const tilemapTranslator = createPluginTranslator(translations);
/**
* Non-React translation function for tilemap 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
* translateTilemap('errors.notFound', 'zh');
*
* // With current locale (auto-detected)
* translateTilemap('toolbar.save');
*
* // With parameters
* translateTilemap('layers.layerCount', undefined, { count: 5 });
* ```
*/
export function translateTilemap(
key: string,
locale?: Locale,
params?: TranslationParams
): string {
const targetLocale = locale || getCurrentLocale();
return tilemapTranslator(key, targetLocale, params);
}
// Re-export for external use
// 重新导出供外部使用
export { getCurrentLocale } from '@esengine/editor-runtime';

View File

@@ -0,0 +1,181 @@
/**
* English translations for Tilemap Editor
* 瓦片地图编辑器英文翻译
*/
export const en = {
// ========================================
// Panel
// ========================================
panel: {
title: 'Tilemap Editor',
noTilemapSelected: 'No tilemap selected',
details: 'Details',
search: 'Search'
},
// ========================================
// Tools
// ========================================
tools: {
tileMode: 'Tile editing mode',
collisionMode: 'Collision editing mode',
tile: 'Tile',
collision: 'Collision',
draw: 'Draw',
drawTile: 'Draw tiles',
drawCollision: 'Draw collision',
eraser: 'Eraser',
eraseTile: 'Erase tiles',
eraseCollision: 'Erase collision',
fill: 'Fill',
fillTile: 'Fill tiles',
fillCollision: 'Fill collision',
rectangle: 'Rectangle',
rectangleTile: 'Rectangle draw',
rectangleCollision: 'Rectangle collision',
select: 'Select',
selectRegion: 'Select region'
},
// ========================================
// Tileset
// ========================================
tileset: {
activeTileset: 'Active Tileset',
showGrid: 'Show grid',
search: 'Search',
none: '(None)',
addTileset: '+ Add Tileset...',
zoom: 'Zoom {{zoom}}:1',
selector: 'Tileset Selector',
selectTileset: 'Select Tileset',
selected: 'Selected: {{width}}×{{height}}'
},
// ========================================
// Collision Mode
// ========================================
collisionMode: {
title: 'Collision Edit Mode',
drawHint: 'Use brush to draw collision areas',
eraseHint: 'Use eraser to clear collision'
},
// ========================================
// Layers
// ========================================
layers: {
title: 'Layers',
addLayer: 'Add Layer',
layerCount: 'Layers ({{count}})',
layer: 'Layer',
layerNumber: 'Layer {{number}}',
editingCollision: 'Currently editing collision',
drawingLayer: 'Currently drawing layer',
moveUp: 'Move layer up',
moveDown: 'Move layer down',
delete: 'Delete layer',
duplicate: 'Duplicate layer',
hide: 'Hide layer',
show: 'Show layer'
},
// ========================================
// Layer Properties
// ========================================
layerProperties: {
title: 'Selected Layer',
name: 'Name',
editName: 'Double-click to edit name',
hideInEditor: 'Hide in editor',
hideInGame: 'Hide in game',
opacity: 'Layer opacity',
collision: 'Layer collision',
overrideThickness: 'Override collision thickness',
overrideOffset: 'Override collision offset',
thicknessOverride: 'Collision thickness override',
offsetOverride: 'Collision offset override',
color: 'Layer color',
material: '{{name}} Material'
},
// ========================================
// Configuration
// ========================================
config: {
title: 'Configuration',
mapWidth: 'Map width',
mapHeight: 'Map height',
tileWidth: 'Tile width',
tileHeight: 'Tile height',
pixelsPerUnit: 'Pixels per unit',
separateByLayer: 'Separate by layer'
},
// ========================================
// Layer Materials
// ========================================
materials: {
title: 'Layer Materials',
default: 'Default Material',
selectMaterial: 'Click to select material',
copyPath: 'Copy path',
clear: 'Clear'
},
// ========================================
// Advanced
// ========================================
advanced: {
title: 'Advanced',
projection: 'Projection mode',
orthographic: 'Orthographic',
isometric: 'Isometric',
hexagonal: 'Hexagonal',
hexSideLength: 'Hex side length',
backgroundColor: 'Background color',
tileGridColor: 'Tile grid color',
multiTileGridColor: 'Multi-tile grid color',
multiTileGridWidth: 'Multi-tile grid width'
},
// ========================================
// Collision Settings
// ========================================
collisionSettings: {
title: 'Collision',
showCollision: 'Show collision'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
toggleGrid: 'Toggle grid',
showCollision: 'Show collision',
save: 'Save (Ctrl+S)',
saveButton: 'Save',
zoomOut: 'Zoom out',
zoomIn: 'Zoom in',
resetView: 'Reset view',
cells: ' cells'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTilesetImage: 'Select tileset image',
selectLayerMaterial: 'Select layer material',
searchAssets: 'Search assets...'
},
// ========================================
// Animation Editor
// ========================================
animation: {
frames: 'Animation Frames',
deleteFrame: 'Delete frame',
addFrameHint: 'Click a tile to add frame'
}
};

View File

@@ -0,0 +1,181 @@
/**
* Spanish translations for Tilemap Editor
* Traducciones en español del editor de mapas de tiles
*/
export const es = {
// ========================================
// Panel
// ========================================
panel: {
title: 'Editor de Tilemap',
noTilemapSelected: 'Ningún tilemap seleccionado',
details: 'Detalles',
search: 'Buscar'
},
// ========================================
// Tools
// ========================================
tools: {
tileMode: 'Modo de edición de tiles',
collisionMode: 'Modo de edición de colisión',
tile: 'Tile',
collision: 'Colisión',
draw: 'Dibujar',
drawTile: 'Dibujar tiles',
drawCollision: 'Dibujar colisión',
eraser: 'Borrador',
eraseTile: 'Borrar tiles',
eraseCollision: 'Borrar colisión',
fill: 'Rellenar',
fillTile: 'Rellenar tiles',
fillCollision: 'Rellenar colisión',
rectangle: 'Rectángulo',
rectangleTile: 'Dibujo rectangular',
rectangleCollision: 'Colisión rectangular',
select: 'Seleccionar',
selectRegion: 'Seleccionar región'
},
// ========================================
// Tileset
// ========================================
tileset: {
activeTileset: 'Tileset activo',
showGrid: 'Mostrar cuadrícula',
search: 'Buscar',
none: '(Ninguno)',
addTileset: '+ Agregar Tileset...',
zoom: 'Zoom {{zoom}}:1',
selector: 'Selector de Tileset',
selectTileset: 'Seleccionar Tileset',
selected: 'Seleccionado: {{width}}×{{height}}'
},
// ========================================
// Collision Mode
// ========================================
collisionMode: {
title: 'Modo de edición de colisión',
drawHint: 'Use el pincel para dibujar áreas de colisión',
eraseHint: 'Use el borrador para eliminar colisión'
},
// ========================================
// Layers
// ========================================
layers: {
title: 'Capas',
addLayer: 'Agregar capa',
layerCount: 'Capas ({{count}})',
layer: 'Capa',
layerNumber: 'Capa {{number}}',
editingCollision: 'Editando colisión actualmente',
drawingLayer: 'Dibujando en capa actualmente',
moveUp: 'Mover capa arriba',
moveDown: 'Mover capa abajo',
delete: 'Eliminar capa',
duplicate: 'Duplicar capa',
hide: 'Ocultar capa',
show: 'Mostrar capa'
},
// ========================================
// Layer Properties
// ========================================
layerProperties: {
title: 'Capa seleccionada',
name: 'Nombre',
editName: 'Doble clic para editar nombre',
hideInEditor: 'Ocultar en editor',
hideInGame: 'Ocultar en juego',
opacity: 'Opacidad de capa',
collision: 'Colisión de capa',
overrideThickness: 'Anular grosor de colisión',
overrideOffset: 'Anular desplazamiento de colisión',
thicknessOverride: 'Anulación de grosor de colisión',
offsetOverride: 'Anulación de desplazamiento de colisión',
color: 'Color de capa',
material: 'Material {{name}}'
},
// ========================================
// Configuration
// ========================================
config: {
title: 'Configuración',
mapWidth: 'Ancho del mapa',
mapHeight: 'Alto del mapa',
tileWidth: 'Ancho de tile',
tileHeight: 'Alto de tile',
pixelsPerUnit: 'Píxeles por unidad',
separateByLayer: 'Separar por capa'
},
// ========================================
// Layer Materials
// ========================================
materials: {
title: 'Materiales de capa',
default: 'Material predeterminado',
selectMaterial: 'Clic para seleccionar material',
copyPath: 'Copiar ruta',
clear: 'Limpiar'
},
// ========================================
// Advanced
// ========================================
advanced: {
title: 'Avanzado',
projection: 'Modo de proyección',
orthographic: 'Ortográfico',
isometric: 'Isométrico',
hexagonal: 'Hexagonal',
hexSideLength: 'Longitud del lado hexagonal',
backgroundColor: 'Color de fondo',
tileGridColor: 'Color de cuadrícula de tiles',
multiTileGridColor: 'Color de cuadrícula multi-tile',
multiTileGridWidth: 'Ancho de cuadrícula multi-tile'
},
// ========================================
// Collision Settings
// ========================================
collisionSettings: {
title: 'Colisión',
showCollision: 'Mostrar colisión'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
toggleGrid: 'Alternar cuadrícula',
showCollision: 'Mostrar colisión',
save: 'Guardar (Ctrl+S)',
saveButton: 'Guardar',
zoomOut: 'Alejar',
zoomIn: 'Acercar',
resetView: 'Restablecer vista',
cells: ' celdas'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTilesetImage: 'Seleccionar imagen de tileset',
selectLayerMaterial: 'Seleccionar material de capa',
searchAssets: 'Buscar assets...'
},
// ========================================
// Animation Editor
// ========================================
animation: {
frames: 'Fotogramas de animación',
deleteFrame: 'Eliminar fotograma',
addFrameHint: 'Clic en un tile para agregar fotograma'
}
};

View File

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

View File

@@ -0,0 +1,181 @@
/**
* Chinese translations for Tilemap Editor
* 瓦片地图编辑器中文翻译
*/
export const zh = {
// ========================================
// Panel
// ========================================
panel: {
title: '瓦片地图编辑器',
noTilemapSelected: '未选择瓦片地图',
details: '细节',
search: '搜索'
},
// ========================================
// Tools
// ========================================
tools: {
tileMode: '瓦片编辑模式',
collisionMode: '碰撞编辑模式',
tile: '瓦片',
collision: '碰撞',
draw: '绘制',
drawTile: '绘制瓦片',
drawCollision: '绘制碰撞',
eraser: '橡皮擦',
eraseTile: '擦除瓦片',
eraseCollision: '擦除碰撞',
fill: '填充',
fillTile: '填充瓦片',
fillCollision: '填充碰撞',
rectangle: '矩形',
rectangleTile: '矩形绘制',
rectangleCollision: '矩形碰撞',
select: '选择',
selectRegion: '选择区域'
},
// ========================================
// Tileset
// ========================================
tileset: {
activeTileset: '活跃瓦片集',
showGrid: '显示网格',
search: '搜索',
none: '(无)',
addTileset: '+ 添加瓦片集...',
zoom: '缩放 {{zoom}}:1',
selector: '瓦片集选择器',
selectTileset: '选择瓦片集',
selected: '已选择: {{width}}×{{height}}'
},
// ========================================
// Collision Mode
// ========================================
collisionMode: {
title: '碰撞编辑模式',
drawHint: '使用画笔绘制碰撞区域',
eraseHint: '使用橡皮擦清除碰撞'
},
// ========================================
// Layers
// ========================================
layers: {
title: '图层',
addLayer: '添加图层',
layerCount: '图层 ({{count}})',
layer: '图层',
layerNumber: '图层 {{number}}',
editingCollision: '当前编辑碰撞',
drawingLayer: '当前绘制图层',
moveUp: '上移图层',
moveDown: '下移图层',
delete: '删除图层',
duplicate: '复制图层',
hide: '隐藏图层',
show: '显示图层'
},
// ========================================
// Layer Properties
// ========================================
layerProperties: {
title: '选定层',
name: '名称',
editName: '双击编辑名称',
hideInEditor: '编辑器中隐藏',
hideInGame: '游戏中隐藏',
opacity: '图层透明度',
collision: '图层碰撞',
overrideThickness: '重载碰撞厚度',
overrideOffset: '重载碰撞偏移',
thicknessOverride: '碰撞厚度重载',
offsetOverride: '碰撞偏移重载',
color: '图层颜色',
material: '{{name}} 材质'
},
// ========================================
// Configuration
// ========================================
config: {
title: '配置',
mapWidth: '地图宽度',
mapHeight: '地图高度',
tileWidth: '瓦片宽度',
tileHeight: '瓦片高度',
pixelsPerUnit: '逻辑单位像素',
separateByLayer: '逐图层分隔'
},
// ========================================
// Layer Materials
// ========================================
materials: {
title: '图层材质',
default: '默认材质',
selectMaterial: '点击选择材质',
copyPath: '复制路径',
clear: '清除'
},
// ========================================
// Advanced
// ========================================
advanced: {
title: '高级',
projection: '投射模式',
orthographic: '正交',
isometric: '等轴测',
hexagonal: '六方',
hexSideLength: '六方格边长度',
backgroundColor: '背景颜色',
tileGridColor: '瓦片网格颜色',
multiTileGridColor: '多瓦片网格颜色',
multiTileGridWidth: '多瓦片网格宽度'
},
// ========================================
// Collision Settings
// ========================================
collisionSettings: {
title: '碰撞',
showCollision: '显示碰撞'
},
// ========================================
// Toolbar
// ========================================
toolbar: {
toggleGrid: '切换网格',
showCollision: '显示碰撞',
save: '保存 (Ctrl+S)',
saveButton: '保存',
zoomOut: '缩小',
zoomIn: '放大',
resetView: '重置视图',
cells: ' 格'
},
// ========================================
// Dialogs
// ========================================
dialogs: {
selectTilesetImage: '选择瓦片集图片',
selectLayerMaterial: '选择图层材质',
searchAssets: '搜索资产...'
},
// ========================================
// Animation Editor
// ========================================
animation: {
frames: '动画帧',
deleteFrame: '删除帧',
addFrameHint: '点击瓦片添加帧'
}
};