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:
@@ -8,6 +8,7 @@ import { Core } from '@esengine/ecs-framework';
|
||||
import { MessageHub, IFileSystemService } from '@esengine/editor-core';
|
||||
import { BlendMode, BuiltInShaders } from '@esengine/material-system';
|
||||
import { useMaterialEditorStore, createDefaultMaterialData } from '../stores/MaterialEditorStore';
|
||||
import { useMaterialLocale } from '../hooks/useMaterialLocale';
|
||||
import { Save, RefreshCw, FolderOpen } from 'lucide-react';
|
||||
import '../styles/MaterialEditorPanel.css';
|
||||
|
||||
@@ -19,25 +20,27 @@ type IFileSystem = {
|
||||
|
||||
/**
|
||||
* 混合模式选项
|
||||
* Blend mode options with translation keys
|
||||
*/
|
||||
const BLEND_MODE_OPTIONS = [
|
||||
{ value: BlendMode.None, label: 'None (Opaque)', labelZh: '无 (不透明)' },
|
||||
{ value: BlendMode.Alpha, label: 'Alpha Blend', labelZh: 'Alpha 混合' },
|
||||
{ value: BlendMode.Additive, label: 'Additive', labelZh: '叠加' },
|
||||
{ value: BlendMode.Multiply, label: 'Multiply', labelZh: '正片叠底' },
|
||||
{ value: BlendMode.Screen, label: 'Screen', labelZh: '滤色' },
|
||||
{ value: BlendMode.PremultipliedAlpha, label: 'Premultiplied Alpha', labelZh: '预乘 Alpha' },
|
||||
{ value: BlendMode.None, labelKey: 'blendModes.none' },
|
||||
{ value: BlendMode.Alpha, labelKey: 'blendModes.alpha' },
|
||||
{ value: BlendMode.Additive, labelKey: 'blendModes.additive' },
|
||||
{ value: BlendMode.Multiply, labelKey: 'blendModes.multiply' },
|
||||
{ value: BlendMode.Screen, labelKey: 'blendModes.screen' },
|
||||
{ value: BlendMode.PremultipliedAlpha, labelKey: 'blendModes.premultipliedAlpha' },
|
||||
];
|
||||
|
||||
/**
|
||||
* 内置着色器选项
|
||||
* Built-in shader options with translation keys
|
||||
*/
|
||||
const BUILT_IN_SHADER_OPTIONS = [
|
||||
{ value: BuiltInShaders.DefaultSprite, label: 'Default Sprite', labelZh: '默认精灵' },
|
||||
{ value: BuiltInShaders.Grayscale, label: 'Grayscale', labelZh: '灰度' },
|
||||
{ value: BuiltInShaders.Tint, label: 'Tint', labelZh: '着色' },
|
||||
{ value: BuiltInShaders.Flash, label: 'Flash', labelZh: '闪烁' },
|
||||
{ value: BuiltInShaders.Outline, label: 'Outline', labelZh: '描边' },
|
||||
{ value: BuiltInShaders.DefaultSprite, labelKey: 'shaders.defaultSprite' },
|
||||
{ value: BuiltInShaders.Grayscale, labelKey: 'shaders.grayscale' },
|
||||
{ value: BuiltInShaders.Tint, labelKey: 'shaders.tint' },
|
||||
{ value: BuiltInShaders.Flash, labelKey: 'shaders.flash' },
|
||||
{ value: BuiltInShaders.Outline, labelKey: 'shaders.outline' },
|
||||
];
|
||||
|
||||
/** Custom shader indicator value. | 自定义着色器指示值。 */
|
||||
@@ -47,7 +50,8 @@ interface MaterialEditorPanelProps {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps) {
|
||||
export function MaterialEditorPanel({ locale: _locale }: MaterialEditorPanelProps) {
|
||||
const { t } = useMaterialLocale();
|
||||
const {
|
||||
currentFilePath,
|
||||
pendingFilePath,
|
||||
@@ -61,8 +65,6 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
updateMaterialProperty,
|
||||
} = useMaterialEditorStore();
|
||||
|
||||
const isZh = locale === 'zh';
|
||||
|
||||
// 加载材质文件
|
||||
const loadMaterialFile = useCallback(async (filePath: string) => {
|
||||
setLoading(true);
|
||||
@@ -136,7 +138,7 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
return (
|
||||
<div className="material-editor-panel loading">
|
||||
<RefreshCw className="spin" size={24} />
|
||||
<span>{isZh ? '加载中...' : 'Loading...'}</span>
|
||||
<span>{t('panel.loading')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -145,7 +147,7 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
if (!materialData) {
|
||||
return (
|
||||
<div className="material-editor-panel empty">
|
||||
<span>{isZh ? '双击 .mat 文件打开材质编辑器' : 'Double-click a .mat file to open the material editor'}</span>
|
||||
<span>{t('panel.emptyState')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -163,10 +165,10 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
className="toolbar-button"
|
||||
onClick={saveMaterialFile}
|
||||
disabled={!isDirty}
|
||||
title={isZh ? '保存 (Ctrl+S)' : 'Save (Ctrl+S)'}
|
||||
title={t('panel.saveTooltip')}
|
||||
>
|
||||
<Save size={16} />
|
||||
<span>{isZh ? '保存' : 'Save'}</span>
|
||||
<span>{t('panel.save')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,10 +177,10 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
<div className="material-editor-content">
|
||||
{/* 基本属性 */}
|
||||
<div className="property-section">
|
||||
<div className="section-header">{isZh ? '基本属性' : 'Basic Properties'}</div>
|
||||
<div className="section-header">{t('properties.basicTitle')}</div>
|
||||
|
||||
<div className="property-row">
|
||||
<label>{isZh ? '名称' : 'Name'}</label>
|
||||
<label>{t('properties.name')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={materialData.name}
|
||||
@@ -187,7 +189,7 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
</div>
|
||||
|
||||
<div className="property-row">
|
||||
<label>{isZh ? '着色器' : 'Shader'}</label>
|
||||
<label>{t('properties.shader')}</label>
|
||||
<div className="shader-selector">
|
||||
<select
|
||||
value={typeof materialData.shader === 'string' ? CUSTOM_SHADER_VALUE : materialData.shader}
|
||||
@@ -205,11 +207,11 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
>
|
||||
{BUILT_IN_SHADER_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{isZh ? opt.labelZh : opt.label}
|
||||
{t(opt.labelKey)}
|
||||
</option>
|
||||
))}
|
||||
<option value={CUSTOM_SHADER_VALUE}>
|
||||
{isZh ? '自定义着色器...' : 'Custom Shader...'}
|
||||
{t('properties.customShader')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -218,13 +220,13 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
{/* Custom shader path input */}
|
||||
{typeof materialData.shader === 'string' && (
|
||||
<div className="property-row">
|
||||
<label>{isZh ? '着色器路径' : 'Shader Path'}</label>
|
||||
<label>{t('properties.shaderPath')}</label>
|
||||
<div className="file-input-row">
|
||||
<input
|
||||
type="text"
|
||||
value={materialData.shader}
|
||||
onChange={(e) => updateMaterialProperty('shader', e.target.value)}
|
||||
placeholder={isZh ? '输入 .shader 文件路径' : 'Enter .shader file path'}
|
||||
placeholder={t('properties.shaderPathPlaceholder')}
|
||||
/>
|
||||
<button
|
||||
className="browse-button"
|
||||
@@ -232,7 +234,7 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
// TODO: Implement file browser dialog
|
||||
// 这里可以集成编辑器的文件选择对话框
|
||||
}}
|
||||
title={isZh ? '浏览...' : 'Browse...'}
|
||||
title={t('properties.browse')}
|
||||
>
|
||||
<FolderOpen size={14} />
|
||||
</button>
|
||||
@@ -241,14 +243,14 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
)}
|
||||
|
||||
<div className="property-row">
|
||||
<label>{isZh ? '混合模式' : 'Blend Mode'}</label>
|
||||
<label>{t('properties.blendMode')}</label>
|
||||
<select
|
||||
value={materialData.blendMode}
|
||||
onChange={(e) => updateMaterialProperty('blendMode', Number(e.target.value))}
|
||||
>
|
||||
{BLEND_MODE_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{isZh ? opt.labelZh : opt.label}
|
||||
{t(opt.labelKey)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -257,11 +259,11 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
|
||||
{/* Uniform 参数 */}
|
||||
<div className="property-section">
|
||||
<div className="section-header">{isZh ? 'Uniform 参数' : 'Uniform Parameters'}</div>
|
||||
<div className="section-header">{t('uniforms.title')}</div>
|
||||
|
||||
{Object.keys(materialData.uniforms || {}).length === 0 ? (
|
||||
<div className="empty-uniforms">
|
||||
{isZh ? '该着色器没有自定义参数' : 'This shader has no custom parameters'}
|
||||
{t('uniforms.empty')}
|
||||
</div>
|
||||
) : (
|
||||
Object.entries(materialData.uniforms || {}).map(([key, uniform]) => (
|
||||
@@ -275,9 +277,9 @@ export function MaterialEditorPanel({ locale = 'en' }: MaterialEditorPanelProps)
|
||||
|
||||
{/* 文件信息 */}
|
||||
<div className="property-section">
|
||||
<div className="section-header">{isZh ? '文件信息' : 'File Info'}</div>
|
||||
<div className="section-header">{t('fileInfo.title')}</div>
|
||||
<div className="property-row file-path">
|
||||
<label>{isZh ? '路径' : 'Path'}</label>
|
||||
<label>{t('fileInfo.path')}</label>
|
||||
<span title={currentFilePath || ''}>
|
||||
{currentFilePath?.split(/[\\/]/).pop() || '-'}
|
||||
</span>
|
||||
|
||||
5
packages/material-editor/src/hooks/index.ts
Normal file
5
packages/material-editor/src/hooks/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Material Editor Hooks
|
||||
* 材质编辑器钩子导出
|
||||
*/
|
||||
export { useMaterialLocale, translateMaterial } from './useMaterialLocale';
|
||||
169
packages/material-editor/src/hooks/useMaterialLocale.ts
Normal file
169
packages/material-editor/src/hooks/useMaterialLocale.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Material Editor Locale Hook
|
||||
* 材质编辑器语言钩子
|
||||
*
|
||||
* 提供材质编辑器专用的翻译功能
|
||||
*/
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { LocaleService } from '@esengine/editor-core';
|
||||
import { en, zh, es } from '../locales';
|
||||
|
||||
type Locale = 'en' | 'zh' | 'es';
|
||||
type TranslationParams = Record<string, string | number>;
|
||||
|
||||
const translations = { en, zh, es } as const;
|
||||
|
||||
/**
|
||||
* 获取嵌套对象的值
|
||||
* Get nested object value by dot notation key
|
||||
*/
|
||||
function getNestedValue(obj: Record<string, unknown>, key: string): string | undefined {
|
||||
const keys = key.split('.');
|
||||
let current: unknown = obj;
|
||||
|
||||
for (const k of keys) {
|
||||
if (current && typeof current === 'object' && k in current) {
|
||||
current = (current as Record<string, unknown>)[k];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof current === 'string' ? current : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换参数占位符
|
||||
* Replace parameter placeholders in string
|
||||
*/
|
||||
function interpolate(text: string, params?: TranslationParams): string {
|
||||
if (!params) return text;
|
||||
|
||||
return text.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
||||
const value = params[key];
|
||||
return value !== undefined ? String(value) : `{{${key}}}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从 LocaleService 获取当前语言
|
||||
* Try to get current locale from LocaleService
|
||||
*/
|
||||
function tryGetLocaleFromService(): Locale | null {
|
||||
try {
|
||||
// 尝试动态获取 LocaleService
|
||||
const localeService = Core.services.tryResolve(LocaleService);
|
||||
|
||||
if (localeService?.getCurrentLocale) {
|
||||
return localeService.getCurrentLocale() as Locale;
|
||||
}
|
||||
} catch {
|
||||
// LocaleService 不可用
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅语言变化
|
||||
* Subscribe to locale changes
|
||||
*/
|
||||
function subscribeToLocaleChanges(callback: (locale: Locale) => void): (() => void) | undefined {
|
||||
try {
|
||||
const localeService = Core.services.tryResolve(LocaleService);
|
||||
|
||||
if (localeService?.onChange) {
|
||||
return localeService.onChange((newLocale) => {
|
||||
callback(newLocale as Locale);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// LocaleService 不可用
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for accessing material editor translations
|
||||
* 访问材质编辑器翻译的 Hook
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { t, locale } = useMaterialLocale();
|
||||
* return <button title={t('panel.saveTooltip')}>{t('panel.save')}</button>;
|
||||
* ```
|
||||
*/
|
||||
export function useMaterialLocale() {
|
||||
const [locale, setLocale] = useState<Locale>(() => {
|
||||
return tryGetLocaleFromService() || 'en';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// 初始化时获取当前语言
|
||||
const currentLocale = tryGetLocaleFromService();
|
||||
if (currentLocale) {
|
||||
setLocale(currentLocale);
|
||||
}
|
||||
|
||||
// 订阅语言变化
|
||||
const unsubscribe = subscribeToLocaleChanges((newLocale) => {
|
||||
setLocale(newLocale);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 翻译函数
|
||||
* Translation function
|
||||
*
|
||||
* @param key - 翻译键,如 'panel.save'
|
||||
* @param params - 插值参数
|
||||
* @param fallback - 回退文本
|
||||
*/
|
||||
const t = useCallback((key: string, params?: TranslationParams, fallback?: string): string => {
|
||||
const currentTranslations = translations[locale] || translations.en;
|
||||
const value = getNestedValue(currentTranslations as Record<string, unknown>, key);
|
||||
|
||||
if (value) {
|
||||
return interpolate(value, params);
|
||||
}
|
||||
|
||||
// 如果当前语言没有,尝试英文
|
||||
if (locale !== 'en') {
|
||||
const enValue = getNestedValue(translations.en as Record<string, unknown>, key);
|
||||
if (enValue) {
|
||||
return interpolate(enValue, params);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回 fallback 或 key 本身
|
||||
return fallback || key;
|
||||
}, [locale]);
|
||||
|
||||
return { t, locale, setLocale };
|
||||
}
|
||||
|
||||
/**
|
||||
* 非 React 环境下的翻译函数
|
||||
* Translation function for non-React context
|
||||
*/
|
||||
export function translateMaterial(key: string, locale: Locale = 'en', params?: TranslationParams): string {
|
||||
const currentTranslations = translations[locale] || translations.en;
|
||||
const value = getNestedValue(currentTranslations as Record<string, unknown>, key);
|
||||
|
||||
if (value) {
|
||||
return interpolate(value, params);
|
||||
}
|
||||
|
||||
if (locale !== 'en') {
|
||||
const enValue = getNestedValue(translations.en as Record<string, unknown>, key);
|
||||
if (enValue) {
|
||||
return interpolate(enValue, params);
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
@@ -21,9 +21,13 @@ import {
|
||||
FileActionRegistry,
|
||||
InspectorRegistry,
|
||||
IInspectorRegistry,
|
||||
IFileSystemService
|
||||
IFileSystemService,
|
||||
LocaleService
|
||||
} from '@esengine/editor-core';
|
||||
|
||||
// Import locale translations
|
||||
import { en, zh, es } from './locales';
|
||||
|
||||
// Inspector provider
|
||||
import { MaterialAssetInspectorProvider } from './providers/MaterialAssetInspectorProvider';
|
||||
|
||||
@@ -103,6 +107,25 @@ export class MaterialEditorModule implements IEditorModuleLoader {
|
||||
await this.handleCreateMaterialAsset(payload);
|
||||
});
|
||||
}
|
||||
|
||||
// Register translations
|
||||
this.registerTranslations(services);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册插件翻译到 LocaleService
|
||||
* Register plugin translations to LocaleService
|
||||
*/
|
||||
private registerTranslations(services: ServiceContainer): void {
|
||||
try {
|
||||
const localeService = services.tryResolve(LocaleService);
|
||||
if (localeService) {
|
||||
localeService.extendTranslations('material', { en, zh, es });
|
||||
console.info('[MaterialEditorModule] Translations registered');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MaterialEditorModule] Failed to register translations:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
@@ -234,6 +257,7 @@ export const materialEditorModule = new MaterialEditorModule();
|
||||
export { MaterialEditorPanel } from './components/MaterialEditorPanel';
|
||||
export { useMaterialEditorStore, createDefaultMaterialData } from './stores/MaterialEditorStore';
|
||||
export type { MaterialEditorState } from './stores/MaterialEditorStore';
|
||||
export { useMaterialLocale, translateMaterial } from './hooks/useMaterialLocale';
|
||||
|
||||
/**
|
||||
* Material Plugin Manifest
|
||||
|
||||
71
packages/material-editor/src/locales/en.ts
Normal file
71
packages/material-editor/src/locales/en.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Material Editor English Translations
|
||||
* 材质编辑器英文翻译
|
||||
*/
|
||||
export const en = {
|
||||
// Editor Panel
|
||||
panel: {
|
||||
loading: 'Loading...',
|
||||
emptyState: 'Double-click a .mat file to open the material editor',
|
||||
save: 'Save',
|
||||
saveTooltip: 'Save (Ctrl+S)',
|
||||
},
|
||||
|
||||
// Properties Section
|
||||
properties: {
|
||||
basicTitle: 'Basic Properties',
|
||||
name: 'Name',
|
||||
shader: 'Shader',
|
||||
shaderPath: 'Shader Path',
|
||||
shaderPathPlaceholder: 'Enter .shader file path',
|
||||
browse: 'Browse...',
|
||||
blendMode: 'Blend Mode',
|
||||
customShader: 'Custom Shader...',
|
||||
},
|
||||
|
||||
// Uniforms Section
|
||||
uniforms: {
|
||||
title: 'Uniform Parameters',
|
||||
empty: 'This shader has no custom parameters',
|
||||
namePlaceholder: 'Uniform name...',
|
||||
addTooltip: 'Add uniform',
|
||||
removeTooltip: 'Remove uniform',
|
||||
},
|
||||
|
||||
// File Info Section
|
||||
fileInfo: {
|
||||
title: 'File Info',
|
||||
path: 'Path',
|
||||
},
|
||||
|
||||
// Blend Mode Options
|
||||
blendModes: {
|
||||
none: 'None (Opaque)',
|
||||
alpha: 'Alpha Blend',
|
||||
additive: 'Additive',
|
||||
multiply: 'Multiply',
|
||||
screen: 'Screen',
|
||||
premultipliedAlpha: 'Premultiplied Alpha',
|
||||
},
|
||||
|
||||
// Built-in Shader Options
|
||||
shaders: {
|
||||
defaultSprite: 'Default Sprite',
|
||||
grayscale: 'Grayscale',
|
||||
tint: 'Tint',
|
||||
flash: 'Flash',
|
||||
outline: 'Outline',
|
||||
},
|
||||
|
||||
// Inspector Panel
|
||||
inspector: {
|
||||
saveTooltip: 'Save (Ctrl+S)',
|
||||
save: 'Save',
|
||||
reset: 'Reset',
|
||||
resetTooltip: 'Reset changes',
|
||||
basicProperties: 'Basic Properties',
|
||||
uniforms: 'Uniforms',
|
||||
loading: 'Loading...',
|
||||
parseError: 'Failed to parse material file',
|
||||
},
|
||||
};
|
||||
71
packages/material-editor/src/locales/es.ts
Normal file
71
packages/material-editor/src/locales/es.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Material Editor Spanish Translations
|
||||
* 材质编辑器西班牙语翻译
|
||||
*/
|
||||
export const es = {
|
||||
// Editor Panel
|
||||
panel: {
|
||||
loading: 'Cargando...',
|
||||
emptyState: 'Haga doble clic en un archivo .mat para abrir el editor de materiales',
|
||||
save: 'Guardar',
|
||||
saveTooltip: 'Guardar (Ctrl+S)',
|
||||
},
|
||||
|
||||
// Properties Section
|
||||
properties: {
|
||||
basicTitle: 'Propiedades Básicas',
|
||||
name: 'Nombre',
|
||||
shader: 'Shader',
|
||||
shaderPath: 'Ruta del Shader',
|
||||
shaderPathPlaceholder: 'Ingrese la ruta del archivo .shader',
|
||||
browse: 'Explorar...',
|
||||
blendMode: 'Modo de Mezcla',
|
||||
customShader: 'Shader Personalizado...',
|
||||
},
|
||||
|
||||
// Uniforms Section
|
||||
uniforms: {
|
||||
title: 'Parámetros Uniform',
|
||||
empty: 'Este shader no tiene parámetros personalizados',
|
||||
namePlaceholder: 'Nombre del uniform...',
|
||||
addTooltip: 'Agregar uniform',
|
||||
removeTooltip: 'Eliminar uniform',
|
||||
},
|
||||
|
||||
// File Info Section
|
||||
fileInfo: {
|
||||
title: 'Información del Archivo',
|
||||
path: 'Ruta',
|
||||
},
|
||||
|
||||
// Blend Mode Options
|
||||
blendModes: {
|
||||
none: 'Ninguno (Opaco)',
|
||||
alpha: 'Mezcla Alpha',
|
||||
additive: 'Aditivo',
|
||||
multiply: 'Multiplicar',
|
||||
screen: 'Pantalla',
|
||||
premultipliedAlpha: 'Alpha Premultiplicado',
|
||||
},
|
||||
|
||||
// Built-in Shader Options
|
||||
shaders: {
|
||||
defaultSprite: 'Sprite por Defecto',
|
||||
grayscale: 'Escala de Grises',
|
||||
tint: 'Tinte',
|
||||
flash: 'Destello',
|
||||
outline: 'Contorno',
|
||||
},
|
||||
|
||||
// Inspector Panel
|
||||
inspector: {
|
||||
saveTooltip: 'Guardar (Ctrl+S)',
|
||||
save: 'Guardar',
|
||||
reset: 'Restablecer',
|
||||
resetTooltip: 'Restablecer cambios',
|
||||
basicProperties: 'Propiedades Básicas',
|
||||
uniforms: 'Uniforms',
|
||||
loading: 'Cargando...',
|
||||
parseError: 'Error al analizar el archivo de material',
|
||||
},
|
||||
};
|
||||
7
packages/material-editor/src/locales/index.ts
Normal file
7
packages/material-editor/src/locales/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Material Editor Locale Exports
|
||||
* 材质编辑器语言导出
|
||||
*/
|
||||
export { en } from './en';
|
||||
export { zh } from './zh';
|
||||
export { es } from './es';
|
||||
71
packages/material-editor/src/locales/zh.ts
Normal file
71
packages/material-editor/src/locales/zh.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Material Editor Chinese Translations
|
||||
* 材质编辑器中文翻译
|
||||
*/
|
||||
export const zh = {
|
||||
// Editor Panel
|
||||
panel: {
|
||||
loading: '加载中...',
|
||||
emptyState: '双击 .mat 文件打开材质编辑器',
|
||||
save: '保存',
|
||||
saveTooltip: '保存 (Ctrl+S)',
|
||||
},
|
||||
|
||||
// Properties Section
|
||||
properties: {
|
||||
basicTitle: '基本属性',
|
||||
name: '名称',
|
||||
shader: '着色器',
|
||||
shaderPath: '着色器路径',
|
||||
shaderPathPlaceholder: '输入 .shader 文件路径',
|
||||
browse: '浏览...',
|
||||
blendMode: '混合模式',
|
||||
customShader: '自定义着色器...',
|
||||
},
|
||||
|
||||
// Uniforms Section
|
||||
uniforms: {
|
||||
title: 'Uniform 参数',
|
||||
empty: '该着色器没有自定义参数',
|
||||
namePlaceholder: 'Uniform 名称...',
|
||||
addTooltip: '添加 uniform',
|
||||
removeTooltip: '删除 uniform',
|
||||
},
|
||||
|
||||
// File Info Section
|
||||
fileInfo: {
|
||||
title: '文件信息',
|
||||
path: '路径',
|
||||
},
|
||||
|
||||
// Blend Mode Options
|
||||
blendModes: {
|
||||
none: '无 (不透明)',
|
||||
alpha: 'Alpha 混合',
|
||||
additive: '叠加',
|
||||
multiply: '正片叠底',
|
||||
screen: '滤色',
|
||||
premultipliedAlpha: '预乘 Alpha',
|
||||
},
|
||||
|
||||
// Built-in Shader Options
|
||||
shaders: {
|
||||
defaultSprite: '默认精灵',
|
||||
grayscale: '灰度',
|
||||
tint: '着色',
|
||||
flash: '闪烁',
|
||||
outline: '描边',
|
||||
},
|
||||
|
||||
// Inspector Panel
|
||||
inspector: {
|
||||
saveTooltip: '保存 (Ctrl+S)',
|
||||
save: '保存',
|
||||
reset: '重置',
|
||||
resetTooltip: '重置更改',
|
||||
basicProperties: '基本属性',
|
||||
uniforms: 'Uniforms',
|
||||
loading: '加载中...',
|
||||
parseError: '材质文件解析失败',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user