diff --git a/packages/editor-app/src/App.tsx b/packages/editor-app/src/App.tsx index cca2e7aa..51822372 100644 --- a/packages/editor-app/src/App.tsx +++ b/packages/editor-app/src/App.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { Core, Scene } from '@esengine/ecs-framework'; -import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService, ComponentRegistry } from '@esengine/editor-core'; +import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService, ComponentRegistry, LocaleService } from '@esengine/editor-core'; import { SceneInspectorPlugin } from './plugins/SceneInspectorPlugin'; import { SceneHierarchy } from './components/SceneHierarchy'; import { EntityInspector } from './components/EntityInspector'; @@ -8,20 +8,29 @@ import { TauriAPI } from './api/tauri'; import { TransformComponent } from './example-components/TransformComponent'; import { SpriteComponent } from './example-components/SpriteComponent'; import { RigidBodyComponent } from './example-components/RigidBodyComponent'; +import { useLocale } from './hooks/useLocale'; +import { en, zh } from './locales'; import './styles/App.css'; +// 在 App 组件外部初始化 Core 和基础服务 +Core.create({ debug: true }); + +const localeService = new LocaleService(); +localeService.registerTranslations('en', en); +localeService.registerTranslations('zh', zh); +Core.services.registerInstance(LocaleService, localeService); + function App() { const [initialized, setInitialized] = useState(false); const [pluginManager, setPluginManager] = useState(null); const [entityStore, setEntityStore] = useState(null); const [messageHub, setMessageHub] = useState(null); - const [status, setStatus] = useState('Initializing...'); + const { t, locale, changeLocale } = useLocale(); + const [status, setStatus] = useState(t('header.status.initializing')); useEffect(() => { const initializeEditor = async () => { try { - const coreInstance = Core.create({ debug: true }); - const editorScene = new Scene(); Core.setScene(editorScene); @@ -59,7 +68,7 @@ function App() { Core.services.registerInstance(ComponentRegistry, componentRegistry); const pluginMgr = new EditorPluginManager(); - pluginMgr.initialize(coreInstance, Core.services); + pluginMgr.initialize(Core, Core.services); await pluginMgr.installEditor(new SceneInspectorPlugin()); @@ -70,18 +79,14 @@ function App() { setPluginManager(pluginMgr); setEntityStore(entityStore); setMessageHub(messageHub); - setStatus('Editor Ready'); + setStatus(t('header.status.ready')); } catch (error) { console.error('Failed to initialize editor:', error); - setStatus('Initialization Failed'); + setStatus(t('header.status.failed')); } }; initializeEditor(); - - return () => { - Core.destroy(); - }; }, []); const handleCreateEntity = () => { @@ -102,16 +107,24 @@ function App() { } }; + const handleLocaleChange = () => { + const newLocale = locale === 'en' ? 'zh' : 'en'; + changeLocale(newLocale); + }; + return (
-

ECS Framework Editor

+

{t('app.title')}

+
{status} @@ -128,13 +141,13 @@ function App() {
-

Viewport

-

Scene viewport will appear here

+

{t('viewport.title')}

+

{t('viewport.placeholder')}

-

Console

-

Console output will appear here

+

{t('console.title')}

+

{t('console.placeholder')}

@@ -148,9 +161,9 @@ function App() {
- Plugins: {pluginManager?.getAllEditorPlugins().length ?? 0} - Entities: {entityStore?.getAllEntities().length ?? 0} - Core: {initialized ? 'Active' : 'Inactive'} + {t('footer.plugins')}: {pluginManager?.getAllEditorPlugins().length ?? 0} + {t('footer.entities')}: {entityStore?.getAllEntities().length ?? 0} + {t('footer.core')}: {initialized ? t('footer.active') : t('footer.inactive')}
); diff --git a/packages/editor-app/src/components/SceneHierarchy.tsx b/packages/editor-app/src/components/SceneHierarchy.tsx index 2222e679..0d3ceee7 100644 --- a/packages/editor-app/src/components/SceneHierarchy.tsx +++ b/packages/editor-app/src/components/SceneHierarchy.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { Entity } from '@esengine/ecs-framework'; import { EntityStoreService, MessageHub } from '@esengine/editor-core'; +import { useLocale } from '../hooks/useLocale'; import '../styles/SceneHierarchy.css'; interface SceneHierarchyProps { @@ -11,6 +12,7 @@ interface SceneHierarchyProps { export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) { const [entities, setEntities] = useState([]); const [selectedId, setSelectedId] = useState(null); + const { t } = useLocale(); useEffect(() => { const updateEntities = () => { @@ -43,11 +45,11 @@ export function SceneHierarchy({ entityStore, messageHub }: SceneHierarchyProps) return (
-

Scene Hierarchy

+

{t('hierarchy.title')}

{entities.length === 0 ? ( -
No entities in scene
+
{t('hierarchy.empty')}
) : (
    {entities.map(entity => ( diff --git a/packages/editor-app/src/hooks/useLocale.ts b/packages/editor-app/src/hooks/useLocale.ts new file mode 100644 index 00000000..5f3d6c3c --- /dev/null +++ b/packages/editor-app/src/hooks/useLocale.ts @@ -0,0 +1,30 @@ +import { useState, useEffect } from 'react'; +import { Core } from '@esengine/ecs-framework'; +import { LocaleService, type Locale } from '@esengine/editor-core'; + +export function useLocale() { + const localeService = Core.services.resolve(LocaleService); + const [locale, setLocale] = useState(localeService.getCurrentLocale()); + + useEffect(() => { + const unsubscribe = localeService.onChange((newLocale) => { + setLocale(newLocale); + }); + + return unsubscribe; + }, [localeService]); + + const t = (key: string, fallback?: string) => { + return localeService.t(key, fallback); + }; + + const changeLocale = (newLocale: Locale) => { + localeService.setLocale(newLocale); + }; + + return { + locale, + t, + changeLocale + }; +} diff --git a/packages/editor-app/src/locales/en.ts b/packages/editor-app/src/locales/en.ts new file mode 100644 index 00000000..2190b1cc --- /dev/null +++ b/packages/editor-app/src/locales/en.ts @@ -0,0 +1,63 @@ +import type { Translations } from '@esengine/editor-core'; + +export const en: Translations = { + app: { + title: 'ECS Framework Editor' + }, + header: { + toolbar: { + createEntity: 'Create Entity', + deleteEntity: 'Delete Entity' + }, + status: { + initializing: 'Initializing...', + ready: 'Editor Ready', + failed: 'Initialization Failed' + } + }, + hierarchy: { + title: 'Scene Hierarchy', + empty: 'No entities', + loading: 'Loading...' + }, + inspector: { + title: 'Inspector', + empty: 'No entity selected', + entityInfo: { + title: 'Entity Info', + id: 'ID', + name: 'Name', + enabled: 'Enabled', + yes: 'Yes', + no: 'No' + }, + components: { + title: 'Components', + empty: 'No components', + add: 'Add Component', + remove: 'Remove' + } + }, + addComponent: { + title: 'Add Component', + search: 'Search components...', + empty: 'No available components', + cancel: 'Cancel', + add: 'Add Component' + }, + viewport: { + title: 'Viewport', + placeholder: 'Scene viewport will appear here' + }, + console: { + title: 'Console', + placeholder: 'Console output will appear here' + }, + footer: { + plugins: 'Plugins', + entities: 'Entities', + core: 'Core', + active: 'Active', + inactive: 'Inactive' + } +}; diff --git a/packages/editor-app/src/locales/index.ts b/packages/editor-app/src/locales/index.ts new file mode 100644 index 00000000..e470b2ec --- /dev/null +++ b/packages/editor-app/src/locales/index.ts @@ -0,0 +1,2 @@ +export { en } from './en'; +export { zh } from './zh'; diff --git a/packages/editor-app/src/locales/zh.ts b/packages/editor-app/src/locales/zh.ts new file mode 100644 index 00000000..dcbaa462 --- /dev/null +++ b/packages/editor-app/src/locales/zh.ts @@ -0,0 +1,63 @@ +import type { Translations } from '@esengine/editor-core'; + +export const zh: Translations = { + app: { + title: 'ECS 框架编辑器' + }, + header: { + toolbar: { + createEntity: '创建实体', + deleteEntity: '删除实体' + }, + status: { + initializing: '初始化中...', + ready: '编辑器就绪', + failed: '初始化失败' + } + }, + hierarchy: { + title: '场景层级', + empty: '无实体', + loading: '加载中...' + }, + inspector: { + title: '检查器', + empty: '未选择实体', + entityInfo: { + title: '实体信息', + id: 'ID', + name: '名称', + enabled: '启用', + yes: '是', + no: '否' + }, + components: { + title: '组件', + empty: '无组件', + add: '添加组件', + remove: '移除' + } + }, + addComponent: { + title: '添加组件', + search: '搜索组件...', + empty: '无可用组件', + cancel: '取消', + add: '添加组件' + }, + viewport: { + title: '视口', + placeholder: '场景视口将显示在这里' + }, + console: { + title: '控制台', + placeholder: '控制台输出将显示在这里' + }, + footer: { + plugins: '插件', + entities: '实体', + core: '核心', + active: '活跃', + inactive: '未激活' + } +}; diff --git a/packages/editor-core/src/Services/LocaleService.ts b/packages/editor-core/src/Services/LocaleService.ts new file mode 100644 index 00000000..370d5bb3 --- /dev/null +++ b/packages/editor-core/src/Services/LocaleService.ts @@ -0,0 +1,143 @@ +import type { IService } from '@esengine/ecs-framework'; +import { Injectable } from '@esengine/ecs-framework'; +import { createLogger } from '@esengine/ecs-framework'; + +const logger = createLogger('LocaleService'); + +export type Locale = 'en' | 'zh'; + +export interface Translations { + [key: string]: string | Translations; +} + +/** + * 国际化服务 + * + * 管理编辑器的多语言支持 + */ +@Injectable() +export class LocaleService implements IService { + private currentLocale: Locale = 'en'; + private translations: Map = new Map(); + private changeListeners: Set<(locale: Locale) => void> = new Set(); + + constructor() { + const savedLocale = this.loadSavedLocale(); + if (savedLocale) { + this.currentLocale = savedLocale; + } + } + + /** + * 注册语言包 + */ + public registerTranslations(locale: Locale, translations: Translations): void { + this.translations.set(locale, translations); + logger.info(`Registered translations for locale: ${locale}`); + } + + /** + * 获取当前语言 + */ + public getCurrentLocale(): Locale { + return this.currentLocale; + } + + /** + * 设置当前语言 + */ + public setLocale(locale: Locale): void { + if (!this.translations.has(locale)) { + logger.warn(`Translations not found for locale: ${locale}`); + return; + } + + this.currentLocale = locale; + this.saveLocale(locale); + + this.changeListeners.forEach(listener => listener(locale)); + + logger.info(`Locale changed to: ${locale}`); + } + + /** + * 翻译文本 + * + * @param key - 翻译键,支持点分隔的路径如 "menu.file.save" + * @param fallback - 如果找不到翻译时的回退文本 + */ + public t(key: string, fallback?: string): string { + const translations = this.translations.get(this.currentLocale); + if (!translations) { + return fallback || key; + } + + const value = this.getNestedValue(translations, key); + if (typeof value === 'string') { + return value; + } + + return fallback || key; + } + + /** + * 监听语言变化 + */ + public onChange(listener: (locale: Locale) => void): () => void { + this.changeListeners.add(listener); + + return () => { + this.changeListeners.delete(listener); + }; + } + + /** + * 获取嵌套对象的值 + */ + private getNestedValue(obj: Translations, path: string): string | Translations | undefined { + const keys = path.split('.'); + let current: any = obj; + + for (const key of keys) { + if (current && typeof current === 'object' && key in current) { + current = current[key]; + } else { + return undefined; + } + } + + return current; + } + + /** + * 从 localStorage 加载保存的语言设置 + */ + private loadSavedLocale(): Locale | null { + try { + const saved = localStorage.getItem('editor-locale'); + if (saved === 'en' || saved === 'zh') { + return saved; + } + } catch (error) { + logger.warn('Failed to load saved locale:', error); + } + return null; + } + + /** + * 保存语言设置到 localStorage + */ + private saveLocale(locale: Locale): void { + try { + localStorage.setItem('editor-locale', locale); + } catch (error) { + logger.warn('Failed to save locale:', error); + } + } + + public dispose(): void { + this.translations.clear(); + this.changeListeners.clear(); + logger.info('LocaleService disposed'); + } +} diff --git a/packages/editor-core/src/index.ts b/packages/editor-core/src/index.ts index aaa2f2be..60c1d5e9 100644 --- a/packages/editor-core/src/index.ts +++ b/packages/editor-core/src/index.ts @@ -12,5 +12,6 @@ export * from './Services/MessageHub'; export * from './Services/SerializerRegistry'; export * from './Services/EntityStoreService'; export * from './Services/ComponentRegistry'; +export * from './Services/LocaleService'; export * from './Types/UITypes';