pi-extensions-i18n 0.4.0 → 0.5.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/README.md +18 -1
- package/README.zh-CN.md +20 -0
- package/SKILL.md +10 -0
- package/package.json +4 -4
- package/src/index.ts +39 -7
- package/src/notice.ts +94 -0
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Independent Pi extensions still need the same operational pieces: a portable con
|
|
|
14
14
|
- `/config:language` interactive command, plus `/config:language en-US` direct selection.
|
|
15
15
|
- Catalog loading and validation requiring both language entries for every message key.
|
|
16
16
|
- Translator interpolation for user-facing UI, command descriptions, and agent prompts.
|
|
17
|
+
- A single notice outlet, `notifyWithSource`, that prefixes every user-visible notice with a short source tag in the package's own label colour. Pi renders `info` notices as dim, unprefixed text, so without a tag you cannot tell which extension spoke.
|
|
17
18
|
|
|
18
19
|
## Install
|
|
19
20
|
|
|
@@ -50,8 +51,24 @@ PI_EXTENSIONS_LOCALE=en-US pi
|
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
## Extension author API
|
|
54
|
+
The package exports the locale and catalog primitives used by the feature packages, plus the shared notice outlet:
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
```ts
|
|
57
|
+
import { notifyWithSource, type NoticeColor, type NoticeSource } from "pi-extensions-i18n";
|
|
58
|
+
|
|
59
|
+
/** Short, unique notice tag for this package. */
|
|
60
|
+
const NOTICE_TAG = "distill";
|
|
61
|
+
/** Label colour; keep it distinct from sibling packages. */
|
|
62
|
+
const NOTICE_COLOR: NoticeColor = "muted";
|
|
63
|
+
/** This package's notice source. */
|
|
64
|
+
const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
|
|
65
|
+
|
|
66
|
+
notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This renders as `[distill] message`: the tag carries the package colour, the message stays untouched, and `level` still decides Pi's yellow `Warning:` / red `Error:` prefix. Colours are only added in tui mode; rpc/print/json get plain text so no ANSI leaks into other frontends. Use `formatNotice({ source, message, mode, theme })` when you only need the rendered string.
|
|
70
|
+
|
|
71
|
+
Other exports:
|
|
55
72
|
|
|
56
73
|
```ts
|
|
57
74
|
import {
|
package/README.zh-CN.md
CHANGED
|
@@ -14,6 +14,7 @@ Pi 扩展公共国际化运行时。它提供基于 catalog 的小型 API,支
|
|
|
14
14
|
- 提供 `/config:language` 交互式命令,也支持 `/config:language en-US` 直接设置。
|
|
15
15
|
- 加载并校验 catalog,要求每个消息 key 同时提供两种语言。
|
|
16
16
|
- 为 UI、命令描述和 Agent prompt 提供用户文案插值。
|
|
17
|
+
- 提供统一的用户提示出口 `notifyWithSource`:给提示加「来源标签 + 固定颜色」,解决 Pi 对 `info` 级提示只显示暗灰无前缀文本、用户分不清消息来自哪个扩展的问题。
|
|
17
18
|
|
|
18
19
|
## 安装
|
|
19
20
|
|
|
@@ -23,6 +24,25 @@ pi install npm:pi-extensions-i18n
|
|
|
23
24
|
|
|
24
25
|
各功能包会自动安装并加载这个公共依赖,因此安装任意使用它的功能包即可使用语言命令。只有不安装其他功能包、想单独使用语言命令时,才需要直接安装本包。
|
|
25
26
|
|
|
27
|
+
## 统一的提示出口
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { notifyWithSource, type NoticeColor, type NoticeSource } from "pi-extensions-i18n";
|
|
31
|
+
|
|
32
|
+
/** 本扩展的提示标签;短且唯一。 */
|
|
33
|
+
const NOTICE_TAG = "distill";
|
|
34
|
+
/** 提示标签颜色;与其它扩展错开。 */
|
|
35
|
+
const NOTICE_COLOR: NoticeColor = "muted";
|
|
36
|
+
/** 本扩展的提示来源。 */
|
|
37
|
+
const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
|
|
38
|
+
|
|
39
|
+
notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
输出形如 `[distill] 提示正文`:标签按扩展固定色,正文保持原样,`level` 仍然决定 Pi 侧的黄色 `Warning:` / 红色 `Error:` 前缀。颜色只在 tui 模式添加,rpc/print/json 模式输出纯文本,不会出现 ANSI 乱码。
|
|
43
|
+
|
|
44
|
+
需要更细粒度控制时用 `formatNotice({ source, message, mode, theme })` 只取文本。
|
|
45
|
+
|
|
26
46
|
安装后重新加载 Pi:
|
|
27
47
|
|
|
28
48
|
```text
|
package/SKILL.md
CHANGED
|
@@ -20,3 +20,13 @@ description: "配置 Pi 扩展共享语言并排查 locale 优先级、持久化
|
|
|
20
20
|
## 验证
|
|
21
21
|
|
|
22
22
|
执行语言命令后观察下一个使用共享 i18n 的扩展文案。若环境变量存在,它会覆盖持久化值;必须先报告这个覆盖关系,不能反复改 JSON。修改 catalog 时,每个 key 必须同时有 `zh-CN` 与 `en-US`,缺失翻译应作为加载错误修复,不能静默 fallback。
|
|
23
|
+
|
|
24
|
+
## 统一提示出口
|
|
25
|
+
|
|
26
|
+
功能包的用户可见提示必须走 `notifyWithSource`(而不是直接 `ctx.ui.notify`),否则 Pi 会把 `info` 级提示渲染成暗灰无前缀文本,用户分不清消息来自哪个扩展:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("failed") });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
每个包用短的唯一 tag 与固定颜色;颜色只在 tui 模式添加(自动处理,不要自己在调用点拼 ANSI)。排查提示显示问题时,先确认调用是否走了这个出口,再看 tag 与 level 是否合理。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extensions-i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared i18n catalog loader & translator for pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"tsconfig.json"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "tsx --test tests
|
|
20
|
+
"test": "tsx --test tests/*.test.ts",
|
|
21
21
|
"typecheck": "tsc --noEmit --pretty false",
|
|
22
22
|
"build": "npm run typecheck",
|
|
23
23
|
"check": "npm run typecheck && npm test && npm pack --dry-run --json > /dev/null"
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"coding-agent"
|
|
55
55
|
],
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@earendil-works/pi-coding-agent": ">=0.80.0
|
|
57
|
+
"@earendil-works/pi-coding-agent": ">=0.80.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
60
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
61
61
|
"@types/node": "24.12.4",
|
|
62
62
|
"tsx": "4.23.1",
|
|
63
63
|
"typescript": "5.9.3"
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { notifyWithSource, type NoticeColor, type NoticeSource } from "./notice.ts";
|
|
9
10
|
|
|
10
11
|
export const SUPPORTED_LOCALES = ["zh-CN", "en-US"] as const;
|
|
11
12
|
export type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
@@ -190,20 +191,32 @@ const commandMessages = loadCatalog(
|
|
|
190
191
|
new URL("../locales/command.json", import.meta.url),
|
|
191
192
|
);
|
|
192
193
|
|
|
194
|
+
/** 本扩展的提示标签;短且唯一,便于在会话里定位来源。 */
|
|
195
|
+
const NOTICE_TAG = "language";
|
|
196
|
+
/** 提示标签颜色;与其它扩展错开。 */
|
|
197
|
+
const NOTICE_COLOR: NoticeColor = "accent";
|
|
198
|
+
/** 本扩展的提示来源。 */
|
|
199
|
+
const NOTICE_SOURCE: NoticeSource = { tag: NOTICE_TAG, color: NOTICE_COLOR };
|
|
200
|
+
|
|
193
201
|
function registerLocaleCommand(pi: ExtensionAPI): void {
|
|
194
202
|
const i18n = createTranslator(commandMessages);
|
|
195
203
|
const command = {
|
|
196
204
|
description: i18n.t("description"),
|
|
197
205
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
198
206
|
if (!ctx.hasUI) {
|
|
199
|
-
ctx
|
|
207
|
+
notifyWithSource({ ctx, source: NOTICE_SOURCE, level: "warning", message: i18n.t("noUi") });
|
|
200
208
|
return;
|
|
201
209
|
}
|
|
202
210
|
|
|
203
211
|
const requested = args.trim();
|
|
204
212
|
const directPreference = requested ? parseLocalePreference(requested) : undefined;
|
|
205
213
|
if (requested && !directPreference) {
|
|
206
|
-
|
|
214
|
+
notifyWithSource({
|
|
215
|
+
ctx,
|
|
216
|
+
source: NOTICE_SOURCE,
|
|
217
|
+
level: "error",
|
|
218
|
+
message: i18n.t("invalid", { value: requested }),
|
|
219
|
+
});
|
|
207
220
|
return;
|
|
208
221
|
}
|
|
209
222
|
|
|
@@ -238,12 +251,19 @@ function registerLocaleCommand(pi: ExtensionAPI): void {
|
|
|
238
251
|
const overrideNotice = envOverride
|
|
239
252
|
? `\n${i18n.t("envOverride", { env: LOCALE_ENV })}`
|
|
240
253
|
: "";
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
notifyWithSource({
|
|
255
|
+
ctx,
|
|
256
|
+
source: NOTICE_SOURCE,
|
|
257
|
+
level: "info",
|
|
258
|
+
message: `${i18n.t("saved", { locale: preference })}${overrideNotice}\n${configPath}`,
|
|
259
|
+
});
|
|
245
260
|
} catch (error) {
|
|
246
|
-
|
|
261
|
+
notifyWithSource({
|
|
262
|
+
ctx,
|
|
263
|
+
source: NOTICE_SOURCE,
|
|
264
|
+
level: "error",
|
|
265
|
+
message: i18n.t("failed", { error: String(error) }),
|
|
266
|
+
});
|
|
247
267
|
}
|
|
248
268
|
},
|
|
249
269
|
};
|
|
@@ -255,3 +275,15 @@ function registerLocaleCommand(pi: ExtensionAPI): void {
|
|
|
255
275
|
export default function piI18n(pi: ExtensionAPI): void {
|
|
256
276
|
registerLocaleCommand(pi);
|
|
257
277
|
}
|
|
278
|
+
|
|
279
|
+
export {
|
|
280
|
+
formatNotice,
|
|
281
|
+
notifyWithSource,
|
|
282
|
+
NOTICE_COLOR_MODE,
|
|
283
|
+
type NoticeColor,
|
|
284
|
+
type NoticeContext,
|
|
285
|
+
type NoticeLevel,
|
|
286
|
+
type NoticeRenderOptions,
|
|
287
|
+
type NoticeSendOptions,
|
|
288
|
+
type NoticeSource,
|
|
289
|
+
} from "./notice.ts";
|
package/src/notice.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户可见提示的统一呈现:给每条提示加「来源标签」,并按级别上色。
|
|
3
|
+
*
|
|
4
|
+
* 背景:Pi 的 ui.notify 只有 info(暗灰、无前缀)/ warning(黄色 Warning:)/ error(红色 Error:)
|
|
5
|
+
* 三种呈现,多数扩展全部用 info,用户在会话里无法分辨消息来自哪个扩展。
|
|
6
|
+
* 这里给每个扩展一个固定短标签与固定颜色,让提示一眼可辨。
|
|
7
|
+
*
|
|
8
|
+
* 本模块不依赖 Pi 的具体实现,只用结构化类型,便于独立测试。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** 提示级别,与 Pi 的 ui.notify 类型一致。 */
|
|
12
|
+
export type NoticeLevel = "info" | "warning" | "error";
|
|
13
|
+
|
|
14
|
+
/** 主题色名;取值是 Pi 主题色的子集,避免依赖具体主题实现。 */
|
|
15
|
+
export type NoticeColor =
|
|
16
|
+
| "accent"
|
|
17
|
+
| "success"
|
|
18
|
+
| "warning"
|
|
19
|
+
| "error"
|
|
20
|
+
| "muted"
|
|
21
|
+
| "dim"
|
|
22
|
+
| "text"
|
|
23
|
+
| "toolTitle";
|
|
24
|
+
|
|
25
|
+
/** 一个扩展的提示来源:短标签 + 固定颜色。 */
|
|
26
|
+
export interface NoticeSource {
|
|
27
|
+
/** 展示在消息前的短标签,例如 "naming"。建议用包名去掉 pi- 前缀。 */
|
|
28
|
+
tag: string;
|
|
29
|
+
/** 该扩展的固定标签颜色,用来在会话里快速定位来源。 */
|
|
30
|
+
color: NoticeColor;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 渲染提示所需的最小 UI 上下文。 */
|
|
34
|
+
export interface NoticeContext {
|
|
35
|
+
/** 运行模式;只有 tui 能安全地显示 ANSI 颜色。 */
|
|
36
|
+
mode?: string;
|
|
37
|
+
ui: {
|
|
38
|
+
/** Pi 的提示出口。 */
|
|
39
|
+
notify(message: string, type?: NoticeLevel): void;
|
|
40
|
+
/** 主题;缺失时不加颜色。 */
|
|
41
|
+
theme?: { fg(color: NoticeColor, text: string): string };
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 一次提示的渲染输入。 */
|
|
46
|
+
export interface NoticeRenderOptions {
|
|
47
|
+
/** 来源标签与颜色。 */
|
|
48
|
+
source: NoticeSource;
|
|
49
|
+
/** 提示正文(已本地化)。 */
|
|
50
|
+
message: string;
|
|
51
|
+
/** 运行模式;非 tui 时输出纯文本。 */
|
|
52
|
+
mode: string | undefined;
|
|
53
|
+
/** 主题;缺失时输出纯文本。 */
|
|
54
|
+
theme: NoticeContext["ui"]["theme"];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 一次带来源的提示调用。 */
|
|
58
|
+
export interface NoticeSendOptions {
|
|
59
|
+
/** 目标 UI 上下文;运行模式与主题从它上面读取。 */
|
|
60
|
+
ctx: NoticeContext;
|
|
61
|
+
/** 来源标签与颜色。 */
|
|
62
|
+
source: NoticeSource;
|
|
63
|
+
/** 提示级别;决定 Pi 侧的前缀与主色。 */
|
|
64
|
+
level: NoticeLevel;
|
|
65
|
+
/** 提示正文(已本地化)。 */
|
|
66
|
+
message: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 只有 TUI 模式能安全地看到 ANSI 颜色。 */
|
|
70
|
+
export const NOTICE_COLOR_MODE = "tui";
|
|
71
|
+
|
|
72
|
+
/** 标签与消息之间的分隔符。 */
|
|
73
|
+
const TAG_SEPARATOR = " ";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 给提示文本加上来源标签与颜色。
|
|
77
|
+
* 非 TUI 模式或没有主题时返回纯文本,避免把 ANSI 序列转发给前端。
|
|
78
|
+
*/
|
|
79
|
+
export function formatNotice(options: NoticeRenderOptions): string {
|
|
80
|
+
const { source, message, mode, theme } = options;
|
|
81
|
+
const tag = `[${source.tag}]`;
|
|
82
|
+
if (mode !== NOTICE_COLOR_MODE || theme === undefined) return `${tag}${TAG_SEPARATOR}${message}`;
|
|
83
|
+
return `${theme.fg(source.color, tag)}${TAG_SEPARATOR}${message}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 统一的提示出口:加来源标签后交给 Pi 的 notify。
|
|
88
|
+
* 级别仍由调用方指定,保证 warning/error 依然带 Pi 自带的黄色/红色前缀。
|
|
89
|
+
*/
|
|
90
|
+
export function notifyWithSource(options: NoticeSendOptions): void {
|
|
91
|
+
const { ctx, source, level, message } = options;
|
|
92
|
+
const text = formatNotice({ source, message, mode: ctx.mode, theme: ctx.ui.theme });
|
|
93
|
+
ctx.ui.notify(text, level);
|
|
94
|
+
}
|