@wsxjs/wsx-i18next 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WSXJS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @wsxjs/wsx-i18next
2
+
3
+ i18next integration for WSXJS components - 为 WSXJS 组件提供 i18next 国际化支持
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @wsxjs/wsx-i18next i18next
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ### 1. 初始化 i18n
14
+
15
+ ```typescript
16
+ import { initI18n } from '@wsxjs/wsx-i18next';
17
+
18
+ initI18n({
19
+ fallbackLng: 'en',
20
+ resources: {
21
+ en: {
22
+ common: { hello: 'Hello' },
23
+ },
24
+ zh: {
25
+ common: { hello: '你好' },
26
+ },
27
+ },
28
+ });
29
+ ```
30
+
31
+ ### 2. 使用装饰器(推荐)
32
+
33
+ ```tsx
34
+ /** @jsxImportSource @wsxjs/wsx-core */
35
+ import { WebComponent, autoRegister } from '@wsxjs/wsx-core';
36
+ import { i18n } from '@wsxjs/wsx-i18next';
37
+
38
+ @i18n('common')
39
+ @autoRegister({ tagName: 'my-component' })
40
+ export class MyComponent extends WebComponent {
41
+ render() {
42
+ return <div>{this.t('hello')}</div>;
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 3. 使用 useTranslation + @state
48
+
49
+ ```tsx
50
+ /** @jsxImportSource @wsxjs/wsx-core */
51
+ import { LightComponent, autoRegister, state } from '@wsxjs/wsx-core';
52
+ import { i18n, useTranslation } from '@wsxjs/wsx-i18next';
53
+
54
+ @autoRegister({ tagName: 'my-component' })
55
+ export class MyComponent extends LightComponent {
56
+ private translation = useTranslation('common');
57
+ @state private currentLang: string = i18n.language;
58
+ private unsubscribe?: () => void;
59
+
60
+ protected onConnected(): void {
61
+ this.unsubscribe = i18n.on('languageChanged', (lng) => {
62
+ this.currentLang = lng;
63
+ });
64
+ }
65
+
66
+ protected onDisconnected(): void {
67
+ if (this.unsubscribe) {
68
+ this.unsubscribe();
69
+ }
70
+ }
71
+
72
+ render() {
73
+ return <div>{this.translation.t('hello')}</div>;
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### 4. 使用 Mixin
79
+
80
+ ```tsx
81
+ /** @jsxImportSource @wsxjs/wsx-core */
82
+ import { WebComponent, autoRegister } from '@wsxjs/wsx-core';
83
+ import { withI18n } from '@wsxjs/wsx-i18next';
84
+
85
+ @autoRegister({ tagName: 'my-component' })
86
+ export class MyComponent extends withI18n(WebComponent, 'common') {
87
+ render() {
88
+ return <div>{this.t('hello')}</div>;
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## API
94
+
95
+ ### initI18n(config?)
96
+
97
+ 初始化 i18next 实例。
98
+
99
+ ### i18n (装饰器)
100
+
101
+ 为组件自动注入翻译功能。
102
+
103
+ ```tsx
104
+ @i18n('namespace')
105
+ export class MyComponent extends WebComponent {
106
+ render() {
107
+ return <div>{this.t('key')}</div>;
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### useTranslation(namespace?)
113
+
114
+ 创建翻译对象,API 与 react-i18next 兼容。
115
+
116
+ ### withI18n(Base, defaultNamespace?)
117
+
118
+ 为基类添加 i18n 支持的 mixin。
119
+
120
+ ## 响应式机制
121
+
122
+ - `@i18n` 装饰器:自动订阅语言变化并触发重渲染
123
+ - `useTranslation` + `@state`:手动订阅,通过更新 `@state` 触发重渲染
124
+ - `withI18n`:自动订阅语言变化并触发重渲染
125
+
126
+ ## 更多信息
127
+
128
+ 查看 [RFC-0029](../../docs/rfcs/0029-i18next-integration.md) 了解完整设计。
129
+
@@ -0,0 +1,114 @@
1
+ import i18n$1, { TFunction, i18n } from 'i18next';
2
+ export { default as i18nInstance } from 'i18next';
3
+ import { WebComponent, LightComponent } from '@wsxjs/wsx-core';
4
+
5
+ /**
6
+ * TypeScript 类型定义
7
+ */
8
+
9
+ /**
10
+ * i18n 配置接口
11
+ */
12
+ interface I18nConfig {
13
+ fallbackLng?: string;
14
+ debug?: boolean;
15
+ resources?: Record<string, Record<string, object>>;
16
+ backend?: {
17
+ loadPath?: string;
18
+ };
19
+ ns?: string[];
20
+ defaultNS?: string;
21
+ interpolation?: {
22
+ escapeValue?: boolean;
23
+ };
24
+ }
25
+ /**
26
+ * useTranslation 返回类型
27
+ */
28
+ interface UseTranslationResponse {
29
+ t: TFunction;
30
+ i18n: i18n;
31
+ ready: boolean;
32
+ }
33
+
34
+ /**
35
+ * i18next 配置和初始化
36
+ */
37
+
38
+ /**
39
+ * 初始化 i18next
40
+ * @param config 配置选项
41
+ * @returns i18n 实例
42
+ */
43
+ declare function initI18n(config?: I18nConfig): typeof i18n$1;
44
+
45
+ /**
46
+ * @i18n 装饰器 - 自动为组件注入翻译功能
47
+ */
48
+ /**
49
+ * @i18n 装饰器 - 自动为组件注入翻译功能
50
+ *
51
+ * 使用方式:
52
+ * ```tsx
53
+ * @i18n('common')
54
+ * export class MyComponent extends WebComponent {
55
+ * render() {
56
+ * return <div>{this.t('welcome')}</div>;
57
+ * }
58
+ * }
59
+ * ```
60
+ *
61
+ * @param namespace 命名空间,默认为 'common'
62
+ * @returns 类装饰器
63
+ */
64
+ declare function i18nDecorator(namespace?: string): <T extends {
65
+ new (...args: any[]): any;
66
+ }>(constructor: T) => T;
67
+
68
+ /**
69
+ * useTranslation 函数(API 与 react-i18next 兼容)
70
+ */
71
+
72
+ /**
73
+ * useTranslation - API 与 react-i18next 兼容的翻译函数
74
+ *
75
+ * **重要说明**:
76
+ * - 这不是 React hook,而是 WSXJS 的普通函数
77
+ * - API 设计参考 react-i18next,但实现方式完全不同
78
+ * - 在 WSXJS 中,需要配合 @state 或 @i18n 装饰器实现响应式
79
+ * - 不会自动响应语言变化,需要手动订阅 languageChanged 事件
80
+ *
81
+ * @param namespace 命名空间,默认为 'common'
82
+ * @returns 翻译对象
83
+ */
84
+ declare function useTranslation(namespace?: string): UseTranslationResponse;
85
+
86
+ /**
87
+ * Mixin API - 为基类添加 i18n 支持
88
+ */
89
+
90
+ /**
91
+ * 为任何继承自 WebComponent 或 LightComponent 的类添加 i18n 支持
92
+ *
93
+ * 使用方式:
94
+ * ```tsx
95
+ * export class MyComponent extends withI18n(WebComponent, 'common') {
96
+ * render() {
97
+ * return <div>{this.t('welcome')}</div>;
98
+ * }
99
+ * }
100
+ *
101
+ * export class MyLightComponent extends withI18n(LightComponent, 'common') {
102
+ * render() {
103
+ * return <div>{this.t('welcome')}</div>;
104
+ * }
105
+ * }
106
+ * ```
107
+ *
108
+ * @param Base 基类(WebComponent 或 LightComponent)
109
+ * @param defaultNamespace 默认命名空间
110
+ * @returns 增强后的类
111
+ */
112
+ declare function withI18n<T extends typeof WebComponent | typeof LightComponent>(Base: T, defaultNamespace?: string): T;
113
+
114
+ export { type I18nConfig, type UseTranslationResponse, i18nDecorator as i18n, i18nDecorator, initI18n, useTranslation, withI18n };
@@ -0,0 +1,114 @@
1
+ import i18n$1, { TFunction, i18n } from 'i18next';
2
+ export { default as i18nInstance } from 'i18next';
3
+ import { WebComponent, LightComponent } from '@wsxjs/wsx-core';
4
+
5
+ /**
6
+ * TypeScript 类型定义
7
+ */
8
+
9
+ /**
10
+ * i18n 配置接口
11
+ */
12
+ interface I18nConfig {
13
+ fallbackLng?: string;
14
+ debug?: boolean;
15
+ resources?: Record<string, Record<string, object>>;
16
+ backend?: {
17
+ loadPath?: string;
18
+ };
19
+ ns?: string[];
20
+ defaultNS?: string;
21
+ interpolation?: {
22
+ escapeValue?: boolean;
23
+ };
24
+ }
25
+ /**
26
+ * useTranslation 返回类型
27
+ */
28
+ interface UseTranslationResponse {
29
+ t: TFunction;
30
+ i18n: i18n;
31
+ ready: boolean;
32
+ }
33
+
34
+ /**
35
+ * i18next 配置和初始化
36
+ */
37
+
38
+ /**
39
+ * 初始化 i18next
40
+ * @param config 配置选项
41
+ * @returns i18n 实例
42
+ */
43
+ declare function initI18n(config?: I18nConfig): typeof i18n$1;
44
+
45
+ /**
46
+ * @i18n 装饰器 - 自动为组件注入翻译功能
47
+ */
48
+ /**
49
+ * @i18n 装饰器 - 自动为组件注入翻译功能
50
+ *
51
+ * 使用方式:
52
+ * ```tsx
53
+ * @i18n('common')
54
+ * export class MyComponent extends WebComponent {
55
+ * render() {
56
+ * return <div>{this.t('welcome')}</div>;
57
+ * }
58
+ * }
59
+ * ```
60
+ *
61
+ * @param namespace 命名空间,默认为 'common'
62
+ * @returns 类装饰器
63
+ */
64
+ declare function i18nDecorator(namespace?: string): <T extends {
65
+ new (...args: any[]): any;
66
+ }>(constructor: T) => T;
67
+
68
+ /**
69
+ * useTranslation 函数(API 与 react-i18next 兼容)
70
+ */
71
+
72
+ /**
73
+ * useTranslation - API 与 react-i18next 兼容的翻译函数
74
+ *
75
+ * **重要说明**:
76
+ * - 这不是 React hook,而是 WSXJS 的普通函数
77
+ * - API 设计参考 react-i18next,但实现方式完全不同
78
+ * - 在 WSXJS 中,需要配合 @state 或 @i18n 装饰器实现响应式
79
+ * - 不会自动响应语言变化,需要手动订阅 languageChanged 事件
80
+ *
81
+ * @param namespace 命名空间,默认为 'common'
82
+ * @returns 翻译对象
83
+ */
84
+ declare function useTranslation(namespace?: string): UseTranslationResponse;
85
+
86
+ /**
87
+ * Mixin API - 为基类添加 i18n 支持
88
+ */
89
+
90
+ /**
91
+ * 为任何继承自 WebComponent 或 LightComponent 的类添加 i18n 支持
92
+ *
93
+ * 使用方式:
94
+ * ```tsx
95
+ * export class MyComponent extends withI18n(WebComponent, 'common') {
96
+ * render() {
97
+ * return <div>{this.t('welcome')}</div>;
98
+ * }
99
+ * }
100
+ *
101
+ * export class MyLightComponent extends withI18n(LightComponent, 'common') {
102
+ * render() {
103
+ * return <div>{this.t('welcome')}</div>;
104
+ * }
105
+ * }
106
+ * ```
107
+ *
108
+ * @param Base 基类(WebComponent 或 LightComponent)
109
+ * @param defaultNamespace 默认命名空间
110
+ * @returns 增强后的类
111
+ */
112
+ declare function withI18n<T extends typeof WebComponent | typeof LightComponent>(Base: T, defaultNamespace?: string): T;
113
+
114
+ export { type I18nConfig, type UseTranslationResponse, i18nDecorator as i18n, i18nDecorator, initI18n, useTranslation, withI18n };
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ i18n: () => i18nDecorator,
34
+ i18nDecorator: () => i18nDecorator,
35
+ i18nInstance: () => import_i18next.default,
36
+ initI18n: () => initI18n,
37
+ useTranslation: () => useTranslation,
38
+ withI18n: () => withI18n
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/i18n.ts
43
+ var import_i18next = __toESM(require("i18next"));
44
+ var import_i18next_browser_languagedetector = __toESM(require("i18next-browser-languagedetector"));
45
+ var import_i18next_http_backend = __toESM(require("i18next-http-backend"));
46
+ function initI18n(config = {}) {
47
+ import_i18next.default.use(import_i18next_http_backend.default).use(import_i18next_browser_languagedetector.default).init({
48
+ fallbackLng: "en",
49
+ debug: false,
50
+ interpolation: {
51
+ escapeValue: false
52
+ },
53
+ backend: {
54
+ loadPath: "/locales/{{lng}}/{{ns}}.json"
55
+ },
56
+ ns: ["common", "home", "docs", "examples"],
57
+ defaultNS: "common",
58
+ ...config
59
+ });
60
+ return import_i18next.default;
61
+ }
62
+
63
+ // src/decorator.ts
64
+ function i18nDecorator(namespace = "common") {
65
+ return function(constructor) {
66
+ class I18nEnhanced extends constructor {
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ constructor(...args) {
69
+ super(...args);
70
+ this._i18nNamespace = namespace;
71
+ }
72
+ // 注入 t 方法
73
+ t(key, options) {
74
+ return import_i18next.default.t(key, { ns: this._i18nNamespace, ...options });
75
+ }
76
+ // 注入 i18n 实例
77
+ get i18n() {
78
+ return import_i18next.default;
79
+ }
80
+ // 生命周期:组件连接时订阅语言变化
81
+ onConnected() {
82
+ super.onConnected?.();
83
+ const handler = () => {
84
+ if (this.rerender) {
85
+ this.rerender();
86
+ }
87
+ };
88
+ this._languageChangedHandler = handler;
89
+ const unsubscribe = import_i18next.default.on("languageChanged", handler);
90
+ if (typeof unsubscribe === "function") {
91
+ this._i18nUnsubscribe = unsubscribe;
92
+ } else {
93
+ this._i18nUnsubscribe = () => {
94
+ if (typeof import_i18next.default.off === "function") {
95
+ import_i18next.default.off("languageChanged", handler);
96
+ }
97
+ };
98
+ }
99
+ }
100
+ // 生命周期:组件断开时取消订阅
101
+ onDisconnected() {
102
+ if (this._i18nUnsubscribe && typeof this._i18nUnsubscribe === "function") {
103
+ try {
104
+ this._i18nUnsubscribe();
105
+ } catch {
106
+ const handler = this._languageChangedHandler;
107
+ if (handler && typeof import_i18next.default.off === "function") {
108
+ import_i18next.default.off("languageChanged", handler);
109
+ }
110
+ }
111
+ this._i18nUnsubscribe = void 0;
112
+ } else {
113
+ const handler = this._languageChangedHandler;
114
+ if (handler && typeof import_i18next.default.off === "function") {
115
+ import_i18next.default.off("languageChanged", handler);
116
+ }
117
+ }
118
+ if (this._languageChangedHandler) {
119
+ delete this._languageChangedHandler;
120
+ }
121
+ super.onDisconnected?.();
122
+ }
123
+ }
124
+ Object.setPrototypeOf(I18nEnhanced, constructor);
125
+ Object.defineProperty(I18nEnhanced, "name", {
126
+ value: constructor.name,
127
+ writable: false
128
+ });
129
+ return I18nEnhanced;
130
+ };
131
+ }
132
+
133
+ // src/hooks.ts
134
+ function useTranslation(namespace = "common") {
135
+ const t = (key, options) => {
136
+ return import_i18next.default.t(key, { ns: namespace, ...options });
137
+ };
138
+ return {
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ t,
141
+ i18n: import_i18next.default,
142
+ ready: import_i18next.default.isInitialized
143
+ };
144
+ }
145
+
146
+ // src/mixin.ts
147
+ function withI18n(Base, defaultNamespace = "common") {
148
+ return class extends Base {
149
+ t(key, namespace, options) {
150
+ return import_i18next.default.t(key, { ns: namespace || defaultNamespace, ...options });
151
+ }
152
+ get i18n() {
153
+ return import_i18next.default;
154
+ }
155
+ onConnected() {
156
+ import_i18next.default.on("languageChanged", () => {
157
+ if (this.rerender) {
158
+ this.rerender();
159
+ }
160
+ });
161
+ }
162
+ };
163
+ }
164
+ // Annotate the CommonJS export names for ESM import in node:
165
+ 0 && (module.exports = {
166
+ i18n,
167
+ i18nDecorator,
168
+ i18nInstance,
169
+ initI18n,
170
+ useTranslation,
171
+ withI18n
172
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,130 @@
1
+ // src/i18n.ts
2
+ import i18n from "i18next";
3
+ import LanguageDetector from "i18next-browser-languagedetector";
4
+ import Backend from "i18next-http-backend";
5
+ function initI18n(config = {}) {
6
+ i18n.use(Backend).use(LanguageDetector).init({
7
+ fallbackLng: "en",
8
+ debug: false,
9
+ interpolation: {
10
+ escapeValue: false
11
+ },
12
+ backend: {
13
+ loadPath: "/locales/{{lng}}/{{ns}}.json"
14
+ },
15
+ ns: ["common", "home", "docs", "examples"],
16
+ defaultNS: "common",
17
+ ...config
18
+ });
19
+ return i18n;
20
+ }
21
+
22
+ // src/decorator.ts
23
+ function i18nDecorator(namespace = "common") {
24
+ return function(constructor) {
25
+ class I18nEnhanced extends constructor {
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ constructor(...args) {
28
+ super(...args);
29
+ this._i18nNamespace = namespace;
30
+ }
31
+ // 注入 t 方法
32
+ t(key, options) {
33
+ return i18n.t(key, { ns: this._i18nNamespace, ...options });
34
+ }
35
+ // 注入 i18n 实例
36
+ get i18n() {
37
+ return i18n;
38
+ }
39
+ // 生命周期:组件连接时订阅语言变化
40
+ onConnected() {
41
+ super.onConnected?.();
42
+ const handler = () => {
43
+ if (this.rerender) {
44
+ this.rerender();
45
+ }
46
+ };
47
+ this._languageChangedHandler = handler;
48
+ const unsubscribe = i18n.on("languageChanged", handler);
49
+ if (typeof unsubscribe === "function") {
50
+ this._i18nUnsubscribe = unsubscribe;
51
+ } else {
52
+ this._i18nUnsubscribe = () => {
53
+ if (typeof i18n.off === "function") {
54
+ i18n.off("languageChanged", handler);
55
+ }
56
+ };
57
+ }
58
+ }
59
+ // 生命周期:组件断开时取消订阅
60
+ onDisconnected() {
61
+ if (this._i18nUnsubscribe && typeof this._i18nUnsubscribe === "function") {
62
+ try {
63
+ this._i18nUnsubscribe();
64
+ } catch {
65
+ const handler = this._languageChangedHandler;
66
+ if (handler && typeof i18n.off === "function") {
67
+ i18n.off("languageChanged", handler);
68
+ }
69
+ }
70
+ this._i18nUnsubscribe = void 0;
71
+ } else {
72
+ const handler = this._languageChangedHandler;
73
+ if (handler && typeof i18n.off === "function") {
74
+ i18n.off("languageChanged", handler);
75
+ }
76
+ }
77
+ if (this._languageChangedHandler) {
78
+ delete this._languageChangedHandler;
79
+ }
80
+ super.onDisconnected?.();
81
+ }
82
+ }
83
+ Object.setPrototypeOf(I18nEnhanced, constructor);
84
+ Object.defineProperty(I18nEnhanced, "name", {
85
+ value: constructor.name,
86
+ writable: false
87
+ });
88
+ return I18nEnhanced;
89
+ };
90
+ }
91
+
92
+ // src/hooks.ts
93
+ function useTranslation(namespace = "common") {
94
+ const t = (key, options) => {
95
+ return i18n.t(key, { ns: namespace, ...options });
96
+ };
97
+ return {
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ t,
100
+ i18n,
101
+ ready: i18n.isInitialized
102
+ };
103
+ }
104
+
105
+ // src/mixin.ts
106
+ function withI18n(Base, defaultNamespace = "common") {
107
+ return class extends Base {
108
+ t(key, namespace, options) {
109
+ return i18n.t(key, { ns: namespace || defaultNamespace, ...options });
110
+ }
111
+ get i18n() {
112
+ return i18n;
113
+ }
114
+ onConnected() {
115
+ i18n.on("languageChanged", () => {
116
+ if (this.rerender) {
117
+ this.rerender();
118
+ }
119
+ });
120
+ }
121
+ };
122
+ }
123
+ export {
124
+ i18nDecorator as i18n,
125
+ i18nDecorator,
126
+ i18n as i18nInstance,
127
+ initI18n,
128
+ useTranslation,
129
+ withI18n
130
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@wsxjs/wsx-i18next",
3
+ "version": "0.0.18",
4
+ "description": "i18next integration for WSXJS components",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/wsxjs/wsxjs.git",
18
+ "directory": "packages/wsx-i18next"
19
+ },
20
+ "author": "Albert Li <albert.li@wsxjs.com>",
21
+ "license": "MIT",
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "!**/__tests__",
26
+ "!**/test"
27
+ ],
28
+ "keywords": [
29
+ "wsx",
30
+ "i18next",
31
+ "i18n",
32
+ "internationalization",
33
+ "web-components",
34
+ "typescript"
35
+ ],
36
+ "dependencies": {
37
+ "i18next": "^23.0.0",
38
+ "i18next-browser-languagedetector": "^7.0.0",
39
+ "i18next-http-backend": "^2.0.0",
40
+ "@wsxjs/wsx-core": "0.0.18"
41
+ },
42
+ "devDependencies": {
43
+ "@types/jest": "^29.0.0",
44
+ "@types/node": "^20.0.0",
45
+ "jest": "^29.0.0",
46
+ "ts-jest": "^29.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.0.0"
49
+ },
50
+ "peerDependencies": {
51
+ "i18next": "^23.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.json",
58
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --tsconfig tsconfig.json",
59
+ "test": "jest",
60
+ "test:watch": "jest --watch",
61
+ "test:coverage": "jest --coverage",
62
+ "typecheck": "tsc --noEmit",
63
+ "clean": "rm -rf dist coverage"
64
+ }
65
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @i18n 装饰器 - 自动为组件注入翻译功能
3
+ */
4
+
5
+ import { i18n } from "./i18n";
6
+
7
+ /**
8
+ * @i18n 装饰器 - 自动为组件注入翻译功能
9
+ *
10
+ * 使用方式:
11
+ * ```tsx
12
+ * @i18n('common')
13
+ * export class MyComponent extends WebComponent {
14
+ * render() {
15
+ * return <div>{this.t('welcome')}</div>;
16
+ * }
17
+ * }
18
+ * ```
19
+ *
20
+ * @param namespace 命名空间,默认为 'common'
21
+ * @returns 类装饰器
22
+ */
23
+ export function i18nDecorator(namespace: string = "common") {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ return function <T extends { new (...args: any[]): any }>(constructor: T) {
26
+ class I18nEnhanced extends constructor {
27
+ // 使用 public 而不是 private,因为导出的匿名类类型限制
28
+ public _i18nNamespace!: string;
29
+ public _i18nUnsubscribe?: () => void;
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ constructor(...args: any[]) {
33
+ super(...args);
34
+ // 初始化命名空间
35
+ this._i18nNamespace = namespace;
36
+ }
37
+
38
+ // 注入 t 方法
39
+ public t(key: string, options?: object): string {
40
+ return i18n.t(key, { ns: this._i18nNamespace, ...options });
41
+ }
42
+
43
+ // 注入 i18n 实例
44
+ public get i18n() {
45
+ return i18n;
46
+ }
47
+
48
+ // 生命周期:组件连接时订阅语言变化
49
+ public onConnected(): void {
50
+ // 先调用父类的 onConnected(如果存在)
51
+ super.onConnected?.();
52
+
53
+ // 创建回调函数并保存引用,以便后续取消订阅
54
+ const handler = (() => {
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ if ((this as any).rerender) {
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ (this as any).rerender();
59
+ }
60
+ }) as () => void;
61
+
62
+ // 保存回调引用以便取消订阅
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ (this as any)._languageChangedHandler = handler;
65
+
66
+ // 订阅语言变化事件
67
+ const unsubscribe = i18n.on("languageChanged", handler);
68
+
69
+ // 如果返回的是函数,直接使用;否则使用 off 方法
70
+ if (typeof unsubscribe === "function") {
71
+ this._i18nUnsubscribe = unsubscribe;
72
+ } else {
73
+ // 如果 i18n.on 返回的不是函数,创建一个取消订阅函数
74
+ // 使用 off 方法(如果可用)或空函数
75
+ this._i18nUnsubscribe = () => {
76
+ if (typeof i18n.off === "function") {
77
+ i18n.off("languageChanged", handler);
78
+ }
79
+ };
80
+ }
81
+ }
82
+
83
+ // 生命周期:组件断开时取消订阅
84
+ public onDisconnected(): void {
85
+ // 取消 i18n 订阅
86
+ if (this._i18nUnsubscribe && typeof this._i18nUnsubscribe === "function") {
87
+ try {
88
+ this._i18nUnsubscribe();
89
+ } catch {
90
+ // 如果 unsubscribe 调用失败,尝试使用 off 方法(如果可用)
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ const handler = (this as any)._languageChangedHandler;
93
+ if (handler && typeof i18n.off === "function") {
94
+ i18n.off("languageChanged", handler);
95
+ }
96
+ }
97
+ this._i18nUnsubscribe = undefined;
98
+ } else {
99
+ // 如果 unsubscribe 不是函数,尝试使用 off 方法(如果可用)
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ const handler = (this as any)._languageChangedHandler;
102
+ if (handler && typeof i18n.off === "function") {
103
+ i18n.off("languageChanged", handler);
104
+ }
105
+ }
106
+ // 清理回调引用
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ if ((this as any)._languageChangedHandler) {
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ delete (this as any)._languageChangedHandler;
111
+ }
112
+
113
+ // 调用父类的 onDisconnected(如果存在)
114
+ super.onDisconnected?.();
115
+ }
116
+ }
117
+ // 复制静态属性和方法
118
+ Object.setPrototypeOf(I18nEnhanced, constructor);
119
+ Object.defineProperty(I18nEnhanced, "name", {
120
+ value: constructor.name,
121
+ writable: false,
122
+ });
123
+ return I18nEnhanced as T;
124
+ };
125
+ }
package/src/hooks.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * useTranslation 函数(API 与 react-i18next 兼容)
3
+ */
4
+
5
+ import { i18n } from "./i18n";
6
+ import type { UseTranslationResponse } from "./types";
7
+
8
+ /**
9
+ * useTranslation - API 与 react-i18next 兼容的翻译函数
10
+ *
11
+ * **重要说明**:
12
+ * - 这不是 React hook,而是 WSXJS 的普通函数
13
+ * - API 设计参考 react-i18next,但实现方式完全不同
14
+ * - 在 WSXJS 中,需要配合 @state 或 @i18n 装饰器实现响应式
15
+ * - 不会自动响应语言变化,需要手动订阅 languageChanged 事件
16
+ *
17
+ * @param namespace 命名空间,默认为 'common'
18
+ * @returns 翻译对象
19
+ */
20
+ export function useTranslation(namespace: string = "common"): UseTranslationResponse {
21
+ // 创建一个包装函数,保持 API 兼容性
22
+ const t = (key: string, options?: object): string => {
23
+ // 每次调用 t() 时,i18n.t() 会使用当前的 i18n.language
24
+ // 所以只要组件重渲染,就会得到新的翻译
25
+ return i18n.t(key, { ns: namespace, ...options });
26
+ };
27
+ return {
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ t: t as any,
30
+ i18n,
31
+ ready: i18n.isInitialized,
32
+ };
33
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * i18next 配置和初始化
3
+ */
4
+
5
+ import i18n from "i18next";
6
+ import LanguageDetector from "i18next-browser-languagedetector";
7
+ import Backend from "i18next-http-backend";
8
+ import type { I18nConfig } from "./types";
9
+
10
+ /**
11
+ * 初始化 i18next
12
+ * @param config 配置选项
13
+ * @returns i18n 实例
14
+ */
15
+ export function initI18n(config: I18nConfig = {}): typeof i18n {
16
+ i18n.use(Backend)
17
+ .use(LanguageDetector)
18
+ .init({
19
+ fallbackLng: "en",
20
+ debug: false,
21
+ interpolation: {
22
+ escapeValue: false,
23
+ },
24
+ backend: {
25
+ loadPath: "/locales/{{lng}}/{{ns}}.json",
26
+ },
27
+ ns: ["common", "home", "docs", "examples"],
28
+ defaultNS: "common",
29
+ ...config,
30
+ });
31
+
32
+ return i18n;
33
+ }
34
+
35
+ export { i18n };
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @wsxjs/wsx-i18next - i18next integration for WSXJS components
3
+ *
4
+ * 为 WSXJS 组件提供 i18next 国际化支持
5
+ */
6
+
7
+ // 导出 i18n 实例和初始化函数
8
+ import { i18n as i18nInstance, initI18n } from "./i18n";
9
+ export { i18nInstance, initI18n };
10
+
11
+ // 导出装饰器(使用 i18n 作为装饰器名称,方便使用)
12
+ import { i18nDecorator } from "./decorator";
13
+ export { i18nDecorator as i18n, i18nDecorator };
14
+
15
+ // 导出 hooks
16
+ export { useTranslation } from "./hooks";
17
+
18
+ // 导出 mixin
19
+ export { withI18n } from "./mixin";
20
+
21
+ // 导出类型
22
+ export type { I18nConfig, UseTranslationResponse } from "./types";
package/src/mixin.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Mixin API - 为基类添加 i18n 支持
3
+ */
4
+
5
+ import { i18n } from "./i18n";
6
+ import type { WebComponent, LightComponent } from "@wsxjs/wsx-core";
7
+
8
+ /**
9
+ * 为任何继承自 WebComponent 或 LightComponent 的类添加 i18n 支持
10
+ *
11
+ * 使用方式:
12
+ * ```tsx
13
+ * export class MyComponent extends withI18n(WebComponent, 'common') {
14
+ * render() {
15
+ * return <div>{this.t('welcome')}</div>;
16
+ * }
17
+ * }
18
+ *
19
+ * export class MyLightComponent extends withI18n(LightComponent, 'common') {
20
+ * render() {
21
+ * return <div>{this.t('welcome')}</div>;
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * @param Base 基类(WebComponent 或 LightComponent)
27
+ * @param defaultNamespace 默认命名空间
28
+ * @returns 增强后的类
29
+ */
30
+ export function withI18n<T extends typeof WebComponent | typeof LightComponent>(
31
+ Base: T,
32
+ defaultNamespace: string = "common"
33
+ ): T {
34
+ return class extends Base {
35
+ protected t(key: string, namespace?: string, options?: object): string {
36
+ return i18n.t(key, { ns: namespace || defaultNamespace, ...options });
37
+ }
38
+
39
+ protected get i18n() {
40
+ return i18n;
41
+ }
42
+
43
+ protected onConnected(): void {
44
+ // 订阅语言变化事件,自动触发重渲染
45
+ i18n.on("languageChanged", () => {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ if ((this as any).rerender) {
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ (this as any).rerender();
50
+ }
51
+ });
52
+ }
53
+ } as T;
54
+ }
package/src/types.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * TypeScript 类型定义
3
+ */
4
+
5
+ import type { TFunction, i18n as I18nType } from "i18next";
6
+
7
+ /**
8
+ * i18n 配置接口
9
+ */
10
+ export interface I18nConfig {
11
+ fallbackLng?: string;
12
+ debug?: boolean;
13
+ resources?: Record<string, Record<string, object>>;
14
+ backend?: {
15
+ loadPath?: string;
16
+ };
17
+ ns?: string[];
18
+ defaultNS?: string;
19
+ interpolation?: {
20
+ escapeValue?: boolean;
21
+ };
22
+ }
23
+
24
+ /**
25
+ * useTranslation 返回类型
26
+ */
27
+ export interface UseTranslationResponse {
28
+ t: TFunction;
29
+ i18n: I18nType;
30
+ ready: boolean;
31
+ }