Complete Cocos Creator port with level bundles, themes, and tooling.

Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "e20945fc-1f04-4216-a044-2652e1ebdf78",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,516 @@
import { _decorator, CCBoolean, CCInteger, CCString, Enum, Label } from 'cc';
// @ts-ignore
import { EDITOR } from 'cc/env';
// import ICUType from '../core/ICUType';
// import I18nComponent from './I18nComponent';
// import { DateTimeFormatOptions, NumberFormatOptions, RelativeTimeFormatOptions, RelativeTimeFormatUnit } from '../core/ICUOptions';
// import intl from '../core/IntlManager';
const {
ccclass,
property,
requireComponent,
executeInEditMode,
} = _decorator;
enum VirtualEnum {}
// @ccclass('ICUComponent')
// @executeInEditMode(true)
// @requireComponent(Label)
// export default class ICUComponent extends I18nComponent {
// @property({ visible: false })
// _icuValue = '';
//
// @property
// set icuValue(value: string) {
// this._icuValue = value;
// this.render();
// }
//
// get icuValue(): string {
// return this._icuValue;
// }
//
// @property({ visible: false })
// _type: ICUType = ICUType.DateTime;
//
// @property({ visible: true, type: Enum(ICUType) })
// set type(value: ICUType) {
// this._type = value;
// }
//
// get type(): ICUType {
// return this._type;
// }
//
// protected onLoad() {
// super.onLoad();
// if (this.label && !this._icuValue) {
// this._icuValue = this.label.string;
// }
// }
//
// protected start() {
// this.render();
// }
//
// public render() {
// super.render();
// if (this._icuValue.length === 0) return;
// let translatedString!: string;
// try {
// switch (this.type) {
// case ICUType.Number:
// translatedString = intl.tn(parseFloat(this.icuValue), this.numberFormatOptions);
// break;
// case ICUType.DateTime:
// translatedString = intl.td(new Date(this.icuValue), this.dateTimeFormatOptions);
// break;
// case ICUType.RelativeTime:
// translatedString = intl.tt(
// parseFloat(this.icuValue),
// this.relativeTimeUnit,
// this.relativeTimeFormatOptions,
// );
// break;
// case ICUType.List: {
// const icuList = this.icuValue.split(',');
// translatedString = intl.tl(icuList);
// break;
// }
// default:
// break;
// }
// } catch (e) {
// translatedString = this._icuValue;
// }
// if (EDITOR) {
// this.preview(translatedString);
// } else {
// this.label!.string = translatedString;
// }
// }
//
// // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ auto generate by script don't edit ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// /************************** NumberFormatOptions **************************/
// @property({ visible: false })
// numberFormatOptions: NumberFormatOptions = { useGrouping: false };
// @property({
// type: Enum(VirtualEnum),
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set numberStyle(value: string) {
// this.numberFormatOptions.style = value;
// this.render();
// }
// get numberStyle(): string {
// return this.numberFormatOptions.style as string;
// }
//
// @property({
// type: CCString,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set currency(value: string) {
// this.numberFormatOptions.currency = value;
// this.render();
// }
// get currency(): string {
// return this.numberFormatOptions.currency as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set currencySign(value: string) {
// this.numberFormatOptions.currencySign = value;
// this.render();
// }
// get currencySign(): string {
// return this.numberFormatOptions.currencySign as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set currencyDisplay(value: string) {
// this.numberFormatOptions.currencyDisplay = value;
// this.render();
// }
// get currencyDisplay(): string {
// return this.numberFormatOptions.currencyDisplay as string;
// }
//
// @property({
// type: CCBoolean,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set useGrouping(value: boolean) {
// this.numberFormatOptions.useGrouping = value;
// this.render();
// }
// get useGrouping(): boolean {
// return this.numberFormatOptions.useGrouping as boolean;
// }
//
// @property({
// type: CCInteger,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set minimumIntegerDigits(value: number) {
// this.numberFormatOptions.minimumIntegerDigits = value;
// this.render();
// }
// get minimumIntegerDigits(): number {
// return this.numberFormatOptions.minimumIntegerDigits as number;
// }
//
// @property({
// type: CCInteger,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set minimumFractionDigits(value: number) {
// this.numberFormatOptions.minimumFractionDigits = value;
// this.render();
// }
// get minimumFractionDigits(): number {
// return this.numberFormatOptions.minimumFractionDigits as number;
// }
//
// @property({
// type: CCInteger,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set maximumFractionDigits(value: number) {
// this.numberFormatOptions.maximumFractionDigits = value;
// this.render();
// }
// get maximumFractionDigits(): number {
// return this.numberFormatOptions.maximumFractionDigits as number;
// }
//
// @property({
// type: CCInteger,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set minimumSignificantDigits(value: number) {
// this.numberFormatOptions.minimumSignificantDigits = value;
// this.render();
// }
// get minimumSignificantDigits(): number {
// return this.numberFormatOptions.minimumSignificantDigits as number;
// }
//
// @property({
// type: CCInteger,
// group: 'Number',
// visible(this: ICUComponent) {
// return this.type === ICUType.Number;
// },
// })
// set maximumSignificantDigits(value: number) {
// this.numberFormatOptions.maximumSignificantDigits = value;
// this.render();
// }
// get maximumSignificantDigits(): number {
// return this.numberFormatOptions.maximumSignificantDigits as number;
// }
//
// /************************** DateTimeFormatOptions **************************/
// @property({ visible: false })
// dateTimeFormatOptions: DateTimeFormatOptions = {};
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set dateTimeLocaleMatcher(value: string) {
// this.dateTimeFormatOptions.localeMatcher = value;
// this.render();
// }
// get dateTimeLocaleMatcher(): string {
// return this.dateTimeFormatOptions.localeMatcher as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set weekday(value: string) {
// this.dateTimeFormatOptions.weekday = value;
// this.render();
// }
// get weekday(): string {
// return this.dateTimeFormatOptions.weekday as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set era(value: string) {
// this.dateTimeFormatOptions.era = value;
// this.render();
// }
// get era(): string {
// return this.dateTimeFormatOptions.era as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set year(value: string) {
// this.dateTimeFormatOptions.year = value;
// this.render();
// }
// get year(): string {
// return this.dateTimeFormatOptions.year as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set month(value: string) {
// this.dateTimeFormatOptions.month = value;
// this.render();
// }
// get month(): string {
// return this.dateTimeFormatOptions.month as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set day(value: string) {
// this.dateTimeFormatOptions.day = value;
// this.render();
// }
// get day(): string {
// return this.dateTimeFormatOptions.day as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set hour(value: string) {
// this.dateTimeFormatOptions.hour = value;
// this.render();
// }
// get hour(): string {
// return this.dateTimeFormatOptions.hour as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set minute(value: string) {
// this.dateTimeFormatOptions.minute = value;
// this.render();
// }
// get minute(): string {
// return this.dateTimeFormatOptions.minute as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set second(value: string) {
// this.dateTimeFormatOptions.second = value;
// this.render();
// }
// get second(): string {
// return this.dateTimeFormatOptions.second as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set timeZoneName(value: string) {
// this.dateTimeFormatOptions.timeZoneName = value;
// this.render();
// }
// get timeZoneName(): string {
// return this.dateTimeFormatOptions.timeZoneName as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set formatMatcher(value: string) {
// this.dateTimeFormatOptions.formatMatcher = value;
// this.render();
// }
// get formatMatcher(): string {
// return this.dateTimeFormatOptions.formatMatcher as string;
// }
//
// @property({
// type: CCBoolean,
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set hour12(value: boolean) {
// this.dateTimeFormatOptions.hour12 = value;
// this.render();
// }
// get hour12(): boolean {
// return this.dateTimeFormatOptions.hour12 as boolean;
// }
//
// @property({
// type: CCString,
// group: 'DateTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.DateTime;
// },
// })
// set timeZone(value: string) {
// this.dateTimeFormatOptions.timeZone = value;
// this.render();
// }
// get timeZone(): string {
// return this.dateTimeFormatOptions.timeZone as string;
// }
//
// /************************** RelativeTimeOptions **************************/
// @property({ visible: false })
// relativeTimeFormatOptions: RelativeTimeFormatOptions = {};
//
// @property({
// type: Enum(VirtualEnum),
// group: 'RelativeTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.RelativeTime;
// },
// })
// set relativeTimeLocaleMatcher(value: string) {
// this.relativeTimeFormatOptions.localeMatcher = value;
// this.render();
// }
// get relativeTimeLocaleMatcher(): string {
// return this.relativeTimeFormatOptions.localeMatcher as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'RelativeTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.RelativeTime;
// },
// })
// set numeric(value: string) {
// this.relativeTimeFormatOptions.numeric = value;
// this.render();
// }
// get numeric(): string {
// return this.relativeTimeFormatOptions.numeric as string;
// }
//
// @property({
// type: Enum(VirtualEnum),
// group: 'RelativeTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.RelativeTime;
// },
// })
// set relativeTimeStyle(value: string) {
// this.relativeTimeFormatOptions.style = value;
// this.render();
// }
// get relativeTimeStyle(): string {
// return this.relativeTimeFormatOptions.style as string;
// }
//
// @property({ visible: false })
// _relativeTimeUnit: RelativeTimeFormatUnit = 'second';
//
// @property({
// type: Enum(VirtualEnum),
// group: 'RelativeTime',
// visible(this: ICUComponent) {
// return this.type === ICUType.RelativeTime;
// },
// })
// set relativeTimeUnit(value: string) {
// this._relativeTimeUnit = value;
// this.render();
// }
// get relativeTimeUnit(): string {
// return this._relativeTimeUnit;
// }
//
// // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ auto generate by script don't edit ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// }

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "7a970d99-7d42-4b01-895c-a1fd06c411b5",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,53 @@
import { _decorator, Component, Label, Node } from 'cc';
// @ts-ignore
import { EDITOR } from 'cc/env';
const {
ccclass,
property,
disallowMultiple,
requireComponent,
menu,
} = _decorator;
@ccclass('L10nComponent')
@requireComponent(Label)
@disallowMultiple()
@menu('hidden:LocalizationEditor/L10nComponent')
export default abstract class L10nComponent extends Component {
protected constructor() {
super();
}
@property({
readonly: true,
tooltip: 'i18n:localization-editor.component.string'
})
get string() {
return this.label?.string || '';
}
label?: Label | null = undefined;
protected onLoad() {
this.label = this.node.getComponent(Label);
}
protected start() {
this.render();
}
public render() {}
public preview(value: string) {
if (this.label && EDITOR) {
const originalString = this.label.string;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.label._string = value;
this.label.updateRenderData(true);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
cce.Engine.repaintInEditMode();
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "64a567b7-70a8-4921-894e-b2b550a6ba6f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,66 @@
// @ts-ignore
import { _decorator } from 'cc';
// @ts-ignore
import { EDITOR } from 'cc/env';
import l10n from '../core/l10n-manager';
import L10nComponent from './l10n-component';
const {
ccclass,
property,
executeInEditMode,
menu,
help,
} = _decorator;
@ccclass('L10nLabel')
@executeInEditMode(true)
@menu('LocalizationEditor/L10nLabel')
@help('i18n:localization-editor.component.help')
export default class L10nLabel extends L10nComponent {
@property({ visible: false })
_key = '';
@property({
tooltip: 'i18n:localization-editor.component.key1'
})
set key(value: string) {
this._key = value;
this.render();
}
get key(): string {
return this._key;
}
@property({ visible: false })
_count = 0;
@property({
tooltip: 'i18n:localization-editor.component.count'
})
set count(value: number) {
this._count = value;
this.render();
}
get count(): number {
return this._count;
}
onLoad() {
if (typeof super.onLoad === 'function') {
super.onLoad();
}
}
render() {
const translatedString = l10n.t(this._key, { count: this._count });
if (typeof this.label === 'undefined' || typeof translatedString === 'undefined') {
return;
}
if (EDITOR) {
this.preview(translatedString);
} else {
this.label!.string = translatedString;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "30d83ab3-45f6-472d-8305-9f8280f4f1b8",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,13 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "ef8d37e0-634c-4106-a3ee-13df564c46a3",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {},
"bundleName": "1"
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "8726de39-9138-4457-8098-f4b669c27882",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,14 @@
import l10n from './l10n-manager';
import { game, cclegacy } from 'cc';
// @ts-expect-error
import { EDITOR } from 'cc/env';
if (cclegacy.GAME_VIEW || EDITOR) { // for Editor
// @ts-expect-error we need top level await in Editor
await l10n.createIntl({});
} else { // for Runtime or Preview
game.onPostProjectInitDelegate.add(
() => l10n.createIntl({}),
);
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "2f21bc78-8001-4d8f-a75c-b7da831c91c7",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,65 @@
import { FallbackLanguage, L10nValue } from './l10n-options';
export type FormattedValue = string;
export type TextInfoDirection = 'ltr' | 'rtl';
export interface StandardOption {
count?: number;
// 暂不开放
// context?: string
defaultValue?: L10nValue;
// returnObjects?: boolean;
language?: Intl.BCP47LanguageTag;
fallbackLanguage?: FallbackLanguage;
// 暂不开放
// joinArrays?: string
}
export interface Template {
[key: string]:
| string
| {
[key: string]: StandardOption;
};
}
export interface NumberFormatOptions extends Intl.NumberFormatOptions {
style?: 'decimal' | 'percent' | 'currency' | string;
/**
* 货币代码采用ISO 4217标准
* @see ISO4217Tag
*/
currency?: string;
currencySign?: 'standard' | 'accounting' | string;
currencyDisplay?: 'symbol' | 'code' | 'name' | string;
useGrouping?: boolean;
minimumIntegerDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
minimumSignificantDigits?: number;
maximumSignificantDigits?: number;
}
export interface DateTimeFormatOptions {
localeMatcher?: 'best fit' | 'lookup' | undefined | string;
weekday?: 'long' | 'short' | 'narrow' | undefined | string;
era?: 'long' | 'short' | 'narrow' | undefined | string;
year?: 'numeric' | '2-digit' | undefined | string;
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow' | undefined | string;
day?: 'numeric' | '2-digit' | undefined | string;
hour?: 'numeric' | '2-digit' | undefined | string;
minute?: 'numeric' | '2-digit' | undefined | string;
second?: 'numeric' | '2-digit' | undefined | string;
timeZoneName?: 'long' | 'short' | undefined | string;
formatMatcher?: 'best fit' | 'basic' | undefined | string;
hour12?: boolean | undefined;
timeZone?: string | undefined;
}
export type RelativeTimeFormatUnit = 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | string;
export interface RelativeTimeFormatOptions {
localeMatcher?: 'lookup' | 'best fit' | string;
style?: 'narrow' | 'short' | 'long' | string;
numeric?: 'auto' | 'always' | string;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "dc153188-b6f7-4234-8025-20163cbd0f8c",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,11 @@
/**
* Intl formatting
*/
enum ICUType {
DateTime,
Number,
List,
RelativeTime,
}
export default ICUType;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "e5dc4963-9221-45a4-8f4b-a8af210423e5",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
enum L10nListenEvent {
languageChanged = 'languageChanged',
onMissingKey = 'missingKey',
/**
* store events
*/
// onAdded = 'added',
// onRemoved = 'removed',
}
export default L10nListenEvent;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3415306a-cc9f-4b47-8961-2437afa40e1f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,255 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { createInstance, i18n, InitOptions as I18NextInitOptions } from 'i18next';
// @ts-ignore
import { EDITOR, BUILD, PREVIEW } from 'cc/env';
// @ts-ignore
import { game, assetManager } from 'cc';
import type { L10nOptions, ResourceData, L10nKey, L10nValue, ResourceItem } from './l10n-options';
import {
DateTimeFormatOptions,
FormattedValue,
NumberFormatOptions,
RelativeTimeFormatOptions,
RelativeTimeFormatUnit,
StandardOption,
Template,
TextInfoDirection,
} from './icu-options';
import L10nListenEvent from './l10n-listen-event';
import ResourceDataManager from './resource-data-manager';
import { mainName, pluginName } from './localization-global';
import { ResourceBundle, ResourceList } from './l10n-options';
export class L10nManager {
static LOCAL_STORAGE_LANGUAGE_KEY = `${mainName}/language`;
static readonly DEFAULT_NAMESPACE = 'translation' as const;
static readonly ASSET_NAMESPACE = 'asset' as const;
static readonly ALLOW_NAMESPACE = [L10nManager.DEFAULT_NAMESPACE, L10nManager.ASSET_NAMESPACE] as const;
static l10n: L10nManager = new L10nManager();
/**
* @zh
* i18n 实例
* @en
* i18next instance
*/
private _intl?: i18n = undefined;
private _options: L10nOptions = {};
private resourceList?: ResourceList;
private resourceBundle: ResourceBundle = {};
public resourceDataManager: ResourceDataManager;
private constructor() {
this.resourceDataManager = new ResourceDataManager();
}
public isInitialized(): boolean {
return this._intl?.isInitialized ?? false;
}
public async createIntl(options: L10nOptions) {
const reloadResult = await this.reloadResourceData();
if (!reloadResult) {
return;
}
this._options = options;
this._intl = createInstance();
let localStorageLanguage: string | undefined = undefined;
if (BUILD && !PREVIEW) {
localStorageLanguage = localStorage.getItem(
l10n['_options'].localStorageLanguageKey ?? L10nManager.LOCAL_STORAGE_LANGUAGE_KEY,
);
localStorageLanguage = this.checkLanguage(localStorageLanguage);
}
const defaultLanguage = localStorageLanguage ?? options.language ?? this.resourceList.defaultLanguage;
const fallbackLanguage = options.fallbackLanguage ?? this.resourceList.fallbackLanguage;
const resources = options.resources ?? this.resourceBundle;
const i18nextOptions: I18NextInitOptions = {
lng: defaultLanguage,
fallbackLng: fallbackLanguage,
resources: { ...resources },
ns: L10nManager.ALLOW_NAMESPACE,
defaultNS: L10nManager.DEFAULT_NAMESPACE,
initImmediate: false,
load: 'currentOnly',
};
await this._intl.init(i18nextOptions);
this.setAssetOverrideMap(resources[defaultLanguage][L10nManager.ASSET_NAMESPACE]);
}
public checkLanguage(language: string): string | undefined {
if (!language || language.length === 0 || language === 'null' || language === null || language === 'undefined' || language === undefined) {
return undefined;
}
if (this.resourceList && this.resourceList.languages.length > 0 && this.resourceList.languages.find(it => it === language)) {
return language;
}
return undefined;
}
public cloneIntl(options: L10nOptions) {
if (!this._intl) {
throw new Error('i18next not init, please use \'l10n.createIntl\'');
}
this._intl = this._intl.cloneInstance(options);
}
async reloadResourceData(): Promise<boolean> {
this.resourceList = await this.resourceDataManager.readResourceList();
if (!this.resourceList) {
console.log(`[${pluginName}] not found translate language list, skip init l10n`);
return false;
}
this.resourceBundle = await this.resourceDataManager.readResourceBundle(this.resourceList?.languages ?? []);
if (!this.resourceList?.defaultLanguage) {
console.log(`[${pluginName}] not found translate language data, skip init l10n`);
return false;
}
return true;
}
/** 初始化 i18next */
public config(options: L10nOptions) {
this.cloneIntl(options);
}
public async changeLanguage(language: Intl.BCP47LanguageTag) {
if (!language) {
console.warn(`[${pluginName}] invalid language tag`);
return;
}
console.log(`[${pluginName}] will change language to`, language);
if (this._intl) {
if (this.currentLanguage) {
this.releaseOverrideMap();
}
await this._intl.changeLanguage(language);
this.setAssetOverrideMap(this.resourceBundle[language][L10nManager.ASSET_NAMESPACE]);
if (!EDITOR) {
localStorage.setItem(L10nManager.LOCAL_STORAGE_LANGUAGE_KEY, language);
console.log(`[${pluginName}] game will restart`);
game.restart();
}
} else {
console.log(`[${pluginName}] language data not load, please confirm whether the language data is included in the build`);
}
}
public t(key: L10nKey, options?: StandardOption | Template): L10nValue {
if (!(this._intl?.isInitialized ?? false)) return key;
return this._intl!.t(key, options);
}
/**
* 实验性功能暂不开放
* 数字类ICU
*/
private tn(value: number, options?: NumberFormatOptions): FormattedValue {
if (!(this._intl?.isInitialized ?? false)) return value.toString();
const cloneOptions: NumberFormatOptions = {};
Object.assign(cloneOptions, options);
type NumberFormatOptionsKey = keyof NumberFormatOptions;
for (const key of Object.keys(cloneOptions) as NumberFormatOptionsKey[]) {
if (typeof cloneOptions[key] === 'string' && (cloneOptions[key] as string)!.length === 0) {
delete cloneOptions[key];
} else if (typeof cloneOptions[key] === 'number' && cloneOptions[key] === 0) {
delete cloneOptions[key];
}
}
return new Intl.NumberFormat(this._intl?.language, cloneOptions).format(value);
}
/**
* 实验性功能暂不开放
* 日期/时刻类ICU
*/
private td(date: Date, options?: DateTimeFormatOptions): FormattedValue {
if (!(this._intl?.isInitialized ?? false)) return date.toString();
const cloneOptions: DateTimeFormatOptions = {};
Object.assign(cloneOptions, options);
type DateTimeFormatOptionsKey = keyof DateTimeFormatOptions;
for (const key of Object.keys(cloneOptions) as DateTimeFormatOptionsKey[]) {
if (typeof cloneOptions[key] === 'string' && (cloneOptions[key] as string).length === 0) {
delete cloneOptions[key];
}
}
return new Intl.DateTimeFormat(this._intl?.language, cloneOptions as Intl.DateTimeFormatOptions).format(date);
}
/**
* 实验性功能暂不开放
* 时长类ICU
*/
private tt(value: number, unit: RelativeTimeFormatUnit, options?: RelativeTimeFormatOptions): FormattedValue {
if (!(this._intl?.isInitialized ?? false)) return value.toString();
const cloneOptions: RelativeTimeFormatOptions = {};
Object.assign(cloneOptions, options);
type RelativeTimeFormatOptionsKey = keyof RelativeTimeFormatOptions;
for (const key of Object.keys(cloneOptions) as RelativeTimeFormatOptionsKey[]) {
if (typeof cloneOptions[key] === 'string' && (cloneOptions[key] as string).length === 0) {
delete cloneOptions[key];
}
}
return new Intl.RelativeTimeFormat(this._intl?.language, cloneOptions as Intl.RelativeTimeFormatOptions).format(
value,
unit as Intl.RelativeTimeFormatUnit,
);
}
/**
* 实验性功能暂不开放
* 数组类ICU
*/
private tl(value: string[]): FormattedValue {
if (!(this._intl?.isInitialized ?? false)) return value.toString();
return new Intl.ListFormat(this._intl?.language).format(value);
}
public exists(key: L10nKey): boolean {
return this._intl?.exists(key) ?? false;
}
get currentLanguage(): Intl.BCP47LanguageTag {
return this._intl?.language ?? '';
}
get languages(): readonly Intl.BCP47LanguageTag[] {
return this.resourceList?.languages ?? [];
}
public direction(language?: Intl.BCP47LanguageTag): TextInfoDirection {
return (language ? new Intl.Locale(language) : new Intl.Locale(this._intl!.language)).textInfo()
.direction as TextInfoDirection;
}
public on(event: L10nListenEvent, callback: (...args: any[]) => void) {
this._intl?.on(event, callback);
}
public off(event: L10nListenEvent, callback: (...args: any[]) => void) {
this._intl?.off(event, callback);
}
public getResourceBundle(language: string, namespace: typeof L10nManager.ALLOW_NAMESPACE[number]): ResourceData | undefined {
return this._intl?.getResourceBundle(language, namespace);
}
protected setAssetOverrideMap(assetNamespace: Readonly<ResourceItem>) {
for (const key of Object.keys(assetNamespace)) {
assetManager.assetsOverrideMap.set(key, assetNamespace[key]);
}
}
protected releaseOverrideMap() {
assetManager.assetsOverrideMap.clear();
}
}
const l10n: L10nManager = L10nManager.l10n;
export default l10n;

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "2856aec6-495f-456d-be77-9ce8a6277dc9",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,116 @@
export type L10nKey = string;
export type L10nValue = string;
export interface ResourceList {
defaultLanguage?: Intl.BCP47LanguageTag;
fallbackLanguage?: Intl.BCP47LanguageTag;
languages: Intl.BCP47LanguageTag[];
}
export interface ResourceBundle {
[language: Intl.BCP47LanguageTag]: ResourceData;
}
export interface ResourceData {
[namespace: string]: ResourceItem;
}
export interface ResourceItem {
[key: string]: any;
}
export interface FallbackLanguageObjectList {
[language: string]: readonly string[];
}
export type FallbackLanguage =
| string
| readonly string[]
| FallbackLanguageObjectList
| ((language: Intl.BCP47LanguageTag) => string | readonly string[] | FallbackLanguageObjectList);
export interface L10nOptions {
/**
* Logs info level to console output. Helps finding issues with loading not working.
* @default false
*/
// debug?: boolean;
/**
* Resources to initialize with (if not using loading or not appending using addResourceBundle)
* @default undefined
*/
resources?: ResourceBundle;
/**
* Language to use (overrides language detection)
*/
language?: Intl.BCP47LanguageTag;
/**
* Language to use if translations in user language are not available.
* @default same as language
*/
fallbackLanguage?: false | FallbackLanguage;
/**
* @default IntlManager.LOCAL_STORAGE_LANGUAGE_KEY
*/
localStorageLanguageKey?: string;
/**
* @zh
* 可以对key进行前置处理返回值应该是处理后的key
*
* @en
* Preprocess the key
*
* @param key
* @return string
* onBeforeProcessHandler
*/
beforeTranslate?: (key: L10nKey) => L10nValue;
/**
* @zh
* 对value进行后置处理返回值应该是处理后的value
*
* @en
* Postprocess the value, return the processed value
*
* @param key
* @param value
* @return string
*/
afterTranslate?: (key: string, value: string) => string;
/**
* Allows null values as valid translation
* @default true
*/
returnNull?: boolean;
/**
* Allows empty string as valid translation
* @default true
*/
returnEmptyString?: boolean;
/**
* Allows objects as valid translation result
* @default false
*/
// returnObjects?: boolean;
/**
* Gets called if object was passed in as key but returnObjects was set to false
* @default noop
*/
// returnedObjectHandler?: (key: string, value: string, options: any) => void;
/**
* Char, eg. '\n' that arrays will be joined by
* @default false
*/
// joinArrays?: false | string;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "b461821b-b11b-4b2d-b469-d9d42b137108",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,5 @@
export const pluginName = 'Localization Editor';
export const mainName = 'localization-editor';
export const runtimeBundleName = 'l10n';
export const resourceListPath = 'resource-list';
export const resourceBundlePath = 'resource-bundle';

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "02d9eb4a-80d0-46a8-b3e9-bd4ed15f6f68",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,90 @@
// @ts-ignore
import { BUILD, EDITOR } from 'cc/env';
import { AssetManager, assetManager, JsonAsset, settings, Settings } from 'cc';
import { ResourceBundle, ResourceList } from './l10n-options';
import { mainName, pluginName, resourceBundlePath, resourceListPath, runtimeBundleName } from './localization-global';
export default class ResourceDataManager {
async readResourceList(): Promise<ResourceList> {
if (EDITOR) {
return Editor.Message.request(mainName, 'get-resource-list');
} else if (BUILD) {
console.log(`[${pluginName}] this is build env`);
return this.runtimeLoad(resourceListPath);
} else {
return this.previewLoad(resourceListPath);
}
}
async readResourceBundle(tags: Intl.BCP47LanguageTag[]): Promise<ResourceBundle> {
if (EDITOR) {
return this.editorLoad(tags);
} else if (BUILD) {
return this.runtimeLoad(resourceBundlePath);
} else {
return this.previewLoad(resourceBundlePath);
}
}
/**
* 编辑器模式下使用
* @param locales
*/
async editorLoad(locales: Intl.BCP47LanguageTag[]): Promise<ResourceBundle | undefined> {
return Editor.Message.request(mainName, 'get-resource-bundle', locales);
}
/**
* 构建后运行时使用
* @param fileName
*/
async runtimeLoad<T>(fileName: string): Promise<T | undefined> {
const bundle = await this.getBundle(runtimeBundleName);
if (!bundle) return undefined;
const jsonAsset = await this.getResource(bundle, fileName);
if (!jsonAsset || !jsonAsset.json) return undefined;
return jsonAsset.json as any as T;
}
/**
* 浏览器预览使用
* @param urlPath
*/
async previewLoad<T>(urlPath: string): Promise<T | undefined> {
try {
return await (await fetch(`${mainName}/${urlPath}`)).json() as T;
} catch (e) {
return undefined;
}
}
async checkBundle(bundleName: string): Promise<boolean> {
const queryResult: { bundle: string, version: string }[] | null = settings.querySettings<{ bundle: string, version: string }[]>(Settings.Category.ASSETS, 'preloadBundles');
const bundle = queryResult?.find((it) => it.bundle === bundleName);
return !!bundle;
}
async getBundle(bundleName: string): Promise<AssetManager.Bundle | undefined> {
return new Promise(resolve => {
assetManager.loadBundle(bundleName, (error, bundle: AssetManager.Bundle) => {
if (error) {
resolve(undefined);
} else {
resolve(bundle);
}
});
});
}
async getResource(bundle: AssetManager.Bundle, resourceName: string): Promise<JsonAsset | undefined> {
return new Promise(resolve => {
bundle.load(resourceName, (error, asset: JsonAsset) => {
if (error) {
resolve(undefined);
} else {
resolve(asset);
}
});
});
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "0a95ad24-8abb-499c-99cc-9d959245a167",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "5edb8c82-d058-4174-84ae-a6624ae65a90",
"files": [],
"subMetas": {},
"userData": {
"isBundle": true,
"bundleConfigID": "default"
}
}

View File

@@ -0,0 +1,21 @@
import l10n, { L10nManager } from './core/l10n-manager';
import L10nListenEvent from './core/l10n-listen-event';
import L10nLabel from './components/l10n-label';
export type {
L10nKey,
L10nValue,
ResourceList,
ResourceBundle,
ResourceData,
ResourceItem,
FallbackLanguageObjectList,
FallbackLanguage,
L10nOptions,
} from './core/l10n-options';
export {
l10n,
L10nManager,
L10nLabel,
L10nListenEvent,
};

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "07b549ce-14f5-4964-9fc4-366b956df8d8",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,11 @@
{
"ver": "2.0.0",
"importer": "json",
"imported": true,
"uuid": "fa0fa81e-eaf8-4eb4-8a48-181676ceff01",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,11 @@
{
"ver": "2.0.0",
"importer": "json",
"imported": true,
"uuid": "d2f18e87-d231-4e20-9e54-06a7aa2bd5c1",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "43fd725b-630f-4c60-a864-7cea14073726",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "e9a803d0-7f83-426e-b187-6d1caa791dc6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "495ecfe5-9e27-4b3e-a048-df0d67da1f21",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "9ff49c15-8e75-44e3-955c-3d31f42d783d",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "3d4a4001-ed8e-4131-b245-b6b580406771",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "60bb8e28-ea3e-45ee-8314-911ce01dc8b0",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1 @@
import 'intl-pluralrules';

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "c959445d-20e1-483c-8aa7-08ca07d74dd4",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "1d313b84-d501-43d7-8173-4315142c25d3",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "306c90e6-641a-46df-8383-07e477f49b91",
"files": [],
"subMetas": {},
"userData": {}
}