@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 +21 -0
- package/README.md +129 -0
- package/dist/index.d.mts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +172 -0
- package/dist/index.mjs +130 -0
- package/package.json +65 -0
- package/src/decorator.ts +125 -0
- package/src/hooks.ts +33 -0
- package/src/i18n.ts +35 -0
- package/src/index.ts +22 -0
- package/src/mixin.ts +54 -0
- package/src/types.ts +31 -0
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
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|
package/src/decorator.ts
ADDED
|
@@ -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
|
+
}
|