All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m16s
221 lines
7.3 KiB
TypeScript
221 lines
7.3 KiB
TypeScript
import { resources, JsonAsset, director, SpriteFrame, SpriteAtlas, sp } from 'cc';
|
||
|
||
export class I18nManager {
|
||
|
||
private static _instance: I18nManager;
|
||
spriteFrameCache: Map<string, SpriteFrame> = new Map();
|
||
spineCache: Map<string, sp.SkeletonData> = new Map();
|
||
_sfTasks = new Map<string, Promise<SpriteFrame>>();
|
||
languageData: Record<string, any> = {};
|
||
currentLanguage: string = 'en';
|
||
ready: boolean = false;
|
||
|
||
static get instance() {
|
||
return this._instance || (this._instance = new I18nManager());
|
||
}
|
||
|
||
private constructor() { }
|
||
|
||
|
||
|
||
// yield/await 风格:谁都可以 await,且只加载一次
|
||
ensureI18nSprite(lang: string, name: string): Promise<SpriteFrame> {
|
||
const key = `${lang}_${name}`;
|
||
const path = `i18nSprite/${lang}/${name}/spriteFrame`;
|
||
|
||
const cached = this.spriteFrameCache.get(key);
|
||
if (cached) return Promise.resolve(cached);
|
||
|
||
const pending = this._sfTasks.get(key);
|
||
if (pending) return pending;
|
||
|
||
const task = new Promise<SpriteFrame>((resolve, reject) => {
|
||
resources.load(path, SpriteFrame, (err, sf) => {
|
||
this._sfTasks.delete(key);
|
||
if (err || !sf) return reject(err);
|
||
this.spriteFrameCache.set(key, sf);
|
||
resolve(sf);
|
||
});
|
||
});
|
||
this._sfTasks.set(key, task);
|
||
return task;
|
||
}
|
||
|
||
/**
|
||
* 初始化国际化管理器
|
||
* @param language 初始语言代码
|
||
* @param languageJson 语言数据JSON资源
|
||
*/
|
||
public async init(language: string = 'en', languageJson: JsonAsset = null): Promise<void> {
|
||
this.currentLanguage = language;
|
||
try {
|
||
if (languageJson) {
|
||
this.languageData = languageJson.json;
|
||
}
|
||
|
||
// 预加载资源(目前资源列表为空,可根据需要添加)
|
||
let okSprite = await this.preloadAssets('spriteFrame', [
|
||
'2',
|
||
'3',
|
||
'4',
|
||
'5',
|
||
'6',
|
||
'7',
|
||
'8',
|
||
'9',
|
||
'10',
|
||
'11',
|
||
'12',
|
||
'13',
|
||
'14',
|
||
'15',
|
||
'16',
|
||
'17',
|
||
'18',
|
||
'19',
|
||
'20',
|
||
'21',
|
||
'22',
|
||
'23',
|
||
'24',
|
||
'25',
|
||
'26',
|
||
'27',
|
||
'29',
|
||
'30',
|
||
'98',
|
||
'99',
|
||
'Buy_5',
|
||
'sysgift_completed',
|
||
'sysgift_continue',
|
||
'sysgift_fbs',
|
||
'sysgift_info_fbs',
|
||
'sysgift_received',
|
||
'sysgift_symbols',
|
||
'sysgift_total',
|
||
'sysgift_win',
|
||
]);
|
||
let okAtlas = await this.preloadAssets('atlas', []);
|
||
let okSpine = await this.preloadAssets('spine', []);
|
||
|
||
this.ready = okSprite && okAtlas && okSpine;
|
||
} catch (error) {
|
||
console.error('I18nManager init failed:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 预加载指定类型的资源
|
||
* @param type 资源类型
|
||
* @param names 资源名称列表
|
||
*/
|
||
private async preloadAssets(type: 'spriteFrame' | 'atlas' | 'spine', names: string[]): Promise<boolean> {
|
||
let results = await Promise.all(names.map(name => this.loadAsset(type, name)));
|
||
return results.every(ok => ok);
|
||
}
|
||
|
||
/**
|
||
* 加载单个资源
|
||
* @param type 资源类型
|
||
* @param name 资源名称
|
||
*/
|
||
private loadAsset(type: 'spriteFrame' | 'atlas' | 'spine', name: string): Promise<boolean> {
|
||
return new Promise(resolve => {
|
||
let cacheKey = `${this.currentLanguage}_${name}`;
|
||
|
||
if (type === 'spriteFrame') {
|
||
let path = `i18nSprite/${this.currentLanguage}/${name}/spriteFrame`;
|
||
resources.load(path, SpriteFrame, (err, asset) => {
|
||
if (!err && asset) {
|
||
this.spriteFrameCache.set(cacheKey, asset);
|
||
resolve(true);
|
||
} else {
|
||
console.warn(`[i18n] spriteFrame load failed: ${path}`, err?.message || err);
|
||
resolve(false);
|
||
}
|
||
});
|
||
} else if (type === 'atlas') {
|
||
let path = `i18nSprite/${this.currentLanguage}/${name}`;
|
||
resources.load(path, SpriteAtlas, (err, atlas) => {
|
||
if (!err && atlas) {
|
||
atlas.getSpriteFrames().forEach(frame => {
|
||
this.spriteFrameCache.set(`${this.currentLanguage}_${frame.name}`, frame);
|
||
});
|
||
resolve(true);
|
||
} else {
|
||
console.warn(`[i18n] atlas load failed: ${path}`, err?.message || err);
|
||
resolve(false);
|
||
}
|
||
});
|
||
} else if (type === 'spine') {
|
||
let path = `i18nSpine/${this.currentLanguage}/${name}`;
|
||
resources.load(path, sp.SkeletonData, (err, asset) => {
|
||
if (!err && asset) {
|
||
this.spineCache.set(cacheKey, asset);
|
||
resolve(true);
|
||
} else {
|
||
console.warn(`[i18n] spine load failed: ${path}`, err?.message || err);
|
||
resolve(false);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 设置当前语言
|
||
* @param language 语言代码
|
||
*/
|
||
public setLanguage(language: string): void {
|
||
if (this.languageData[language]) {
|
||
this.currentLanguage = language;
|
||
this.updateSceneRenderers();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 翻译文本
|
||
* @param key 翻译键
|
||
* @returns 翻译后的文本,如果找不到则返回键本身
|
||
*/
|
||
public t(key: string): string {
|
||
return this.languageData[this.currentLanguage]?.[key] || key;
|
||
}
|
||
|
||
public getSpriteFrame(spriteName: string): SpriteFrame {
|
||
let cacheKey = `${this.currentLanguage}_${spriteName}`;
|
||
let cachedFrame = this.spriteFrameCache.get(cacheKey);
|
||
return cachedFrame;
|
||
}
|
||
|
||
/**
|
||
* 更新场景中的所有本地化组件
|
||
* 在语言切换时调用,刷新所有本地化文本和资源
|
||
*/
|
||
public updateSceneRenderers(): void {
|
||
if (!this.ready) {
|
||
console.warn('I18nManager is not ready.');
|
||
return;
|
||
}
|
||
|
||
let scene = director.getScene();
|
||
if (!scene?.isValid) return;
|
||
|
||
// 收集所有本地化组件
|
||
let allLocalizedLabels = [];
|
||
let allLocalizedSprites = [];
|
||
|
||
scene.children.forEach(node => {
|
||
allLocalizedLabels.push(...node.getComponentsInChildren('LocalizedLabel'));
|
||
allLocalizedSprites.push(...node.getComponentsInChildren('LocalizedSprite'));
|
||
});
|
||
|
||
// 更新所有激活的本地化组件
|
||
[...allLocalizedLabels, ...allLocalizedSprites].forEach(component => {
|
||
if (component.node.active) {
|
||
// 调用对应的更新方法
|
||
component.updateLabel?.() || component.updateSprite?.();
|
||
}
|
||
});
|
||
}
|
||
} |