@cogita/plugin-i18n 0.2.0
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 +36 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +3 -0
- package/dist/plugin.js +26 -0
- package/dist/types.js +0 -0
- package/dist/utils.js +72 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wu9o
|
|
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,36 @@
|
|
|
1
|
+
# @cogita/plugin-i18n
|
|
2
|
+
|
|
3
|
+
Lightweight locale-aware UI copy for Cogita themes and third-party components. The plugin injects a message dictionary at build time; it does not translate Markdown content. Content language remains a site-level choice.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defineConfig } from '@cogita/core';
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
site: { title: 'My Knowledge Base', lang: 'en-US' },
|
|
12
|
+
i18n: {
|
|
13
|
+
locale: 'en-US',
|
|
14
|
+
fallbackLocale: 'en-US',
|
|
15
|
+
messages: {
|
|
16
|
+
'en-US': {
|
|
17
|
+
'knowledge.home.search': 'Search knowledge',
|
|
18
|
+
},
|
|
19
|
+
'zh-CN': {
|
|
20
|
+
'knowledge.home.search': '搜索知识',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
theme: '@cogita/theme-knowledge',
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Themes and plugins can import `virtual-cogita-i18n-text`:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { t } from 'virtual-cogita-i18n-text';
|
|
32
|
+
|
|
33
|
+
const label = t('knowledge.home.search', 'Search knowledge');
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Locale resolution checks the full identifier first (for example, `en-US`), then the language prefix (for example, `en`), and finally `fallbackLocale`. When no message is found, the caller-provided fallback is used, so older sites remain safe when the plugin is not enabled.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CogitaPlugin } from '@cogita/shared';
|
|
2
|
+
import type { CogitaPluginConfig } from '@cogita/shared';
|
|
3
|
+
import type { I18nConfig } from '@cogita/shared';
|
|
4
|
+
|
|
5
|
+
/** 生成供主题和第三方组件导入的虚拟国际化模块。 */
|
|
6
|
+
export declare function createI18nRuntimeModule(config: ResolvedI18nConfig): string;
|
|
7
|
+
|
|
8
|
+
export { I18nConfig }
|
|
9
|
+
|
|
10
|
+
/** 国际化运行时可以消费的文案字典。 */
|
|
11
|
+
export declare type I18nMessages = Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
12
|
+
|
|
13
|
+
/** 文案插值变量。 */
|
|
14
|
+
export declare type I18nValues = Readonly<Record<string, string | number>>;
|
|
15
|
+
|
|
16
|
+
/** 规范化语言标识,兼容 `en_US` 这类常见配置写法。 */
|
|
17
|
+
export declare function normalizeLocale(locale: unknown, fallback?: string): string;
|
|
18
|
+
|
|
19
|
+
/** 只保留可安全注入浏览器运行时的文案字典。 */
|
|
20
|
+
export declare function normalizeMessages(value: unknown): I18nMessages;
|
|
21
|
+
|
|
22
|
+
/** 创建站点界面文案国际化插件。 */
|
|
23
|
+
declare function pluginI18n(config: CogitaPluginConfig): CogitaPlugin | null;
|
|
24
|
+
export default pluginI18n;
|
|
25
|
+
export { pluginI18n }
|
|
26
|
+
|
|
27
|
+
/** 规范化后的国际化配置。 */
|
|
28
|
+
export declare interface ResolvedI18nConfig {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
locale: string;
|
|
31
|
+
fallbackLocale: string;
|
|
32
|
+
messages: I18nMessages;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 解析国际化配置,默认以英文作为当前语言和回退语言。 */
|
|
36
|
+
export declare function resolveI18nConfig(config?: I18nConfig, siteLocale?: string): ResolvedI18nConfig;
|
|
37
|
+
|
|
38
|
+
/** 读取一个翻译值,供 Node 侧测试和工具复用相同的回退规则。 */
|
|
39
|
+
export declare function resolveMessage(messages: I18nMessages, locale: string, fallbackLocale: string, key: string, fallback?: string, values?: I18nValues): string;
|
|
40
|
+
|
|
41
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { pluginI18n } from "./plugin.js";
|
|
2
|
+
import { createI18nRuntimeModule, normalizeLocale, normalizeMessages, resolveI18nConfig, resolveMessage } from "./utils.js";
|
|
3
|
+
export { createI18nRuntimeModule, pluginI18n as default, normalizeLocale, normalizeMessages, pluginI18n, resolveI18nConfig, resolveMessage };
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { COGITA_CAPABILITIES, COGITA_VIRTUAL_MODULE_IDS, createCogitaVirtualModule, getCogitaLogger } from "@cogita/shared";
|
|
2
|
+
import { createI18nRuntimeModule, resolveI18nConfig } from "./utils.js";
|
|
3
|
+
function pluginI18n(config) {
|
|
4
|
+
const logger = getCogitaLogger(config);
|
|
5
|
+
if (!config.i18n || false === config.i18n.enabled) {
|
|
6
|
+
logger.info("[I18n Plugin] \u672A\u627E\u5230\u56FD\u9645\u5316\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u754C\u9762\u6587\u6848\u56FD\u9645\u5316");
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const finalConfig = resolveI18nConfig(config.i18n, config.site?.lang);
|
|
10
|
+
logger.info(`[I18n Plugin] \u{4F7F}\u{7528} ${finalConfig.locale} \u{754C}\u{9762}\u{6587}\u{6848}\u{FF0C}\u{56DE}\u{9000}\u{8BED}\u{8A00}\u{4E3A} ${finalConfig.fallbackLocale}`);
|
|
11
|
+
return {
|
|
12
|
+
name: '@cogita/plugin-i18n',
|
|
13
|
+
cogita: {
|
|
14
|
+
providesCapabilities: [
|
|
15
|
+
COGITA_CAPABILITIES.UI_I18N
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
addRuntimeModules () {
|
|
19
|
+
return {
|
|
20
|
+
[COGITA_VIRTUAL_MODULE_IDS.I18N_TEXT]: createCogitaVirtualModule(createI18nRuntimeModule(finalConfig))
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const src_plugin = pluginI18n;
|
|
26
|
+
export { src_plugin as default, pluginI18n };
|
package/dist/types.js
ADDED
|
File without changes
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
function normalizeLocale(locale, fallback = 'en-US') {
|
|
2
|
+
if ('string' != typeof locale) return fallback;
|
|
3
|
+
const normalized = locale.trim().replace(/_/g, '-');
|
|
4
|
+
return normalized || fallback;
|
|
5
|
+
}
|
|
6
|
+
function normalizeMessages(value) {
|
|
7
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return {};
|
|
8
|
+
const messages = {};
|
|
9
|
+
for (const [locale, dictionary] of Object.entries(value)){
|
|
10
|
+
if (!dictionary || 'object' != typeof dictionary || Array.isArray(dictionary)) continue;
|
|
11
|
+
const normalizedDictionary = Object.fromEntries(Object.entries(dictionary).filter(([, text])=>'string' == typeof text));
|
|
12
|
+
if (Object.keys(normalizedDictionary).length > 0) messages[normalizeLocale(locale)] = normalizedDictionary;
|
|
13
|
+
}
|
|
14
|
+
return messages;
|
|
15
|
+
}
|
|
16
|
+
function resolveI18nConfig(config, siteLocale) {
|
|
17
|
+
const fallbackLocale = normalizeLocale(config?.fallbackLocale, 'en-US');
|
|
18
|
+
return {
|
|
19
|
+
enabled: config?.enabled !== false,
|
|
20
|
+
locale: normalizeLocale(config?.locale, normalizeLocale(siteLocale, 'en-US')),
|
|
21
|
+
fallbackLocale,
|
|
22
|
+
messages: normalizeMessages(config?.messages)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createI18nRuntimeModule(config) {
|
|
26
|
+
const serializedConfig = JSON.stringify(config);
|
|
27
|
+
return `export const i18nConfig = ${serializedConfig};
|
|
28
|
+
export const locale = i18nConfig.locale;
|
|
29
|
+
export const fallbackLocale = i18nConfig.fallbackLocale;
|
|
30
|
+
export const messages = i18nConfig.messages;
|
|
31
|
+
|
|
32
|
+
function getLocaleCandidates(value) {
|
|
33
|
+
return value ? [value, value.split('-')[0]] : [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getMessage(key, candidates) {
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const dictionary = messages[candidate];
|
|
39
|
+
if (dictionary && typeof dictionary[key] === 'string') return dictionary[key];
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function t(key, fallback, values) {
|
|
45
|
+
const candidates = [...getLocaleCandidates(locale), ...getLocaleCandidates(fallbackLocale)];
|
|
46
|
+
let text = getMessage(key, candidates) || fallback || key;
|
|
47
|
+
for (const [name, value] of Object.entries(values || {})) {
|
|
48
|
+
text = text.split('{{' + name + '}}').join(String(value));
|
|
49
|
+
}
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const translate = t;
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
function resolveMessage(messages, locale, fallbackLocale, key, fallback = '', values = {}) {
|
|
57
|
+
const candidates = [
|
|
58
|
+
...getLocaleCandidates(locale),
|
|
59
|
+
...getLocaleCandidates(fallbackLocale)
|
|
60
|
+
];
|
|
61
|
+
const message = candidates.map((candidate)=>messages[candidate]?.[key]).find((candidate)=>'string' == typeof candidate);
|
|
62
|
+
let text = message ?? (fallback || key);
|
|
63
|
+
for (const [name, value] of Object.entries(values))text = text.split(`{{${name}}}`).join(String(value));
|
|
64
|
+
return text;
|
|
65
|
+
}
|
|
66
|
+
function getLocaleCandidates(locale) {
|
|
67
|
+
return locale ? [
|
|
68
|
+
locale,
|
|
69
|
+
locale.split('-')[0]
|
|
70
|
+
] : [];
|
|
71
|
+
}
|
|
72
|
+
export { createI18nRuntimeModule, normalizeLocale, normalizeMessages, resolveI18nConfig, resolveMessage };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cogita/plugin-i18n",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "为 Cogita 主题提供英文优先的界面文案国际化能力。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cogita",
|
|
7
|
+
"plugin",
|
|
8
|
+
"i18n",
|
|
9
|
+
"internationalization",
|
|
10
|
+
"localization",
|
|
11
|
+
"rspress"
|
|
12
|
+
],
|
|
13
|
+
"author": "wu9o <https://github.com/wu9o>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/wu9o/cogita.git",
|
|
18
|
+
"directory": "plugins/i18n"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/wu9o/cogita/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/wu9o/cogita/tree/main/plugins/i18n#readme",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@cogita/shared": "0.13.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@rslib/core": "0.11.2",
|
|
48
|
+
"@rspress/core": "^1.45.1",
|
|
49
|
+
"rsbuild-plugin-publint": "^0.3.3",
|
|
50
|
+
"typescript": "^5.0.4"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "rslib build",
|
|
54
|
+
"dev": "rslib build -w",
|
|
55
|
+
"clean": "rm -rf dist",
|
|
56
|
+
"test": "pnpm build && node --test tests/*.test.mjs"
|
|
57
|
+
}
|
|
58
|
+
}
|