gunshi 0.6.2 → 0.8.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 CHANGED
@@ -317,7 +317,7 @@ const customUsageRenderer = ctx => {
317
317
 
318
318
  for (const [key, option] of Object.entries(ctx.options || Object.create(null))) {
319
319
  const shortFlag = option.short ? `-${option.short}, ` : ' '
320
- lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translation(key)}`)
320
+ lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translate(key)}`)
321
321
  }
322
322
 
323
323
  return Promise.resolve(lines.join('\n'))
@@ -365,7 +365,7 @@ const command = {
365
365
  },
366
366
  run: ctx => {
367
367
  const { name = 'World', formal } = ctx.values
368
- const greeting = formal ? ctx.translation('formal') : ctx.translation('informal')
368
+ const greeting = formal ? ctx.translate('formal_greeting') : ctx.translate('informal_greeting')
369
369
  console.log(`${greeting}, ${name}!`)
370
370
  }
371
371
  }
@@ -1,4 +1,4 @@
1
- import { create, deepFreeze, resolveLazyCommand } from "./utils-NHs5DuHk.js";
1
+ import { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, DEFAULT_LOCALE, create, deepFreeze, mapResourceWithBuiltinKey, resolveLazyCommand } from "./utils-CU_LSsUg.js";
2
2
 
3
3
  //#region locales/en-US.json
4
4
  var COMMAND = "COMMAND";
@@ -23,48 +23,47 @@ var en_US_default = {
23
23
  };
24
24
 
25
25
  //#endregion
26
- //#region src/constants.ts
27
- const COMMON_OPTIONS = {
28
- help: {
29
- type: "boolean",
30
- short: "h"
31
- },
32
- version: {
33
- type: "boolean",
34
- short: "v"
26
+ //#region src/translation.ts
27
+ function createTranslationAdapter(options) {
28
+ return new DefaultTranslation(options);
29
+ }
30
+ var DefaultTranslation = class {
31
+ #resources = new Map();
32
+ options;
33
+ constructor(options) {
34
+ this.options = options;
35
+ this.#resources = new Map();
36
+ }
37
+ getResource(locale) {
38
+ return this.#resources.get(locale);
39
+ }
40
+ setResource(locale, resource) {
41
+ this.#resources.set(locale, resource);
42
+ }
43
+ getMessage(locale, key) {
44
+ const resource = this.getResource(locale);
45
+ if (resource) return resource[key];
46
+ return undefined;
47
+ }
48
+ translate(locale, key, _values = create()) {
49
+ /**
50
+ * NOTE:
51
+ * DefaultTranslation support static message only
52
+ * If you want to resolve message with values and use the complex message format,
53
+ * you should inherit this class or implement your own translation adapter.
54
+ */
55
+ return this.getMessage(locale, key) || this.getMessage(this.options.fallbackLocale, key);
35
56
  }
36
57
  };
37
- const COMMAND_OPTIONS_DEFAULT = {
38
- name: undefined,
39
- description: undefined,
40
- version: undefined,
41
- cwd: undefined,
42
- subCommands: undefined,
43
- leftMargin: 2,
44
- middleMargin: 10,
45
- usageOptionType: false,
46
- renderHeader: undefined,
47
- renderUsage: undefined,
48
- renderValidationErrors: undefined
49
- };
50
- const COMMAND_I18N_RESOURCE_KEYS = [
51
- "USAGE",
52
- "COMMAND",
53
- "SUBCOMMAND",
54
- "COMMANDS",
55
- "OPTIONS",
56
- "EXAMPLES",
57
- "FORMORE"
58
- ];
59
58
 
60
59
  //#endregion
61
60
  //#region src/context.ts
62
- const DEFAULT_LOCALE = "en-US";
61
+ const BUILT_IN_PREFIX_CODE = BUILT_IN_PREFIX.codePointAt(0);
63
62
  async function createCommandContext({ options, values, positionals, command, commandOptions, omitted = false }) {
64
63
  /**
65
64
  * tweak the options and values
66
65
  */
67
- const _options = options == null ? undefined : Object.entries(options).reduce((acc, [key, value]) => {
66
+ const _options = Object.entries(options).reduce((acc, [key, value]) => {
68
67
  acc[key] = Object.assign(create(), value);
69
68
  return acc;
70
69
  }, create());
@@ -83,29 +82,31 @@ async function createCommandContext({ options, values, positionals, command, com
83
82
  */
84
83
  const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, commandOptions);
85
84
  const locale = resolveLocale(commandOptions.locale);
85
+ const translationAdapterFactory = commandOptions.translationAdapterFactory || createTranslationAdapter;
86
+ const adapter = translationAdapterFactory({
87
+ locale: locale.toString(),
88
+ fallbackLocale: DEFAULT_LOCALE
89
+ });
86
90
  const localeResources = new Map();
87
- const commandResources = new Map();
88
91
  let builtInLoadedResources;
89
92
  /**
90
93
  * load the built-in locale resources
91
94
  */
92
- localeResources.set(DEFAULT_LOCALE, en_US_default);
95
+ localeResources.set(DEFAULT_LOCALE, mapResourceWithBuiltinKey(en_US_default));
93
96
  if (DEFAULT_LOCALE !== locale.toString()) try {
94
97
  builtInLoadedResources = await import(`../locales/${locale.toString()}.json`, { with: { type: "json" } });
95
- localeResources.set(locale.toString(), builtInLoadedResources);
98
+ localeResources.set(locale.toString(), mapResourceWithBuiltinKey(builtInLoadedResources));
96
99
  } catch {}
97
100
  /**
98
- * define the translation function, which is used to {@link CommandContext.translation}.
101
+ * define the translation function, which is used to {@link CommandContext.translate}.
99
102
  *
100
103
  */
101
- function translation(key) {
102
- if (COMMAND_I18N_RESOURCE_KEYS.includes(key)) {
104
+ function translate(key, values$1 = create()) {
105
+ const strKey = key;
106
+ if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) {
103
107
  const resource = localeResources.get(locale.toString()) || localeResources.get(DEFAULT_LOCALE);
104
- return resource[key] || key;
105
- } else {
106
- const resource = commandResources.get(locale.toString()) || commandResources.get(DEFAULT_LOCALE);
107
- return resource[key] || "";
108
- }
108
+ return resource[strKey] || strKey;
109
+ } else return adapter.translate(locale.toString(), strKey, values$1) || "";
109
110
  }
110
111
  /**
111
112
  * load the sub commands
@@ -130,7 +131,7 @@ async function createCommandContext({ options, values, positionals, command, com
130
131
  positionals,
131
132
  usage,
132
133
  loadCommands,
133
- translation
134
+ translate
134
135
  }));
135
136
  /**
136
137
  * load the command resources
@@ -145,21 +146,18 @@ async function createCommandContext({ options, values, positionals, command, com
145
146
  }, create());
146
147
  defaultCommandResource.description = command.description || "";
147
148
  defaultCommandResource.examples = usage.examples || "";
148
- commandResources.set(DEFAULT_LOCALE, defaultCommandResource);
149
+ adapter.setResource(DEFAULT_LOCALE, defaultCommandResource);
149
150
  const originalResource = await loadCommandResource(ctx, command);
150
151
  if (originalResource) {
151
- const resource = Object.entries(originalResource.options).reduce((res, [key, value]) => {
152
- res[key] = value;
153
- return res;
154
- }, Object.assign(create(), {
152
+ const resource = Object.assign(create(), {
155
153
  description: originalResource.description,
156
154
  examples: originalResource.examples
157
- }));
155
+ }, originalResource);
158
156
  if (builtInLoadedResources) {
159
157
  resource.help = builtInLoadedResources.help;
160
158
  resource.version = builtInLoadedResources.version;
161
159
  }
162
- commandResources.set(locale.toString(), resource);
160
+ adapter.setResource(locale.toString(), resource);
163
161
  }
164
162
  return ctx;
165
163
  }
@@ -175,4 +173,4 @@ async function loadCommandResource(ctx, command) {
175
173
  }
176
174
 
177
175
  //#endregion
178
- export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, createCommandContext };
176
+ export { DefaultTranslation, createCommandContext };
package/lib/context.d.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import { ArgOptions, ArgValues } from 'args-tokens';
2
- import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-CxaX4FVV.js';
2
+ import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-CX4RmDVT.js';
3
3
 
4
- /**
5
- * The default locale string, which format is BCP 47 language tag
6
- */
7
- declare const DEFAULT_LOCALE = "en-US";
8
4
  /**
9
5
  * Parameters of {@link createCommandContext}
10
6
  */
@@ -15,7 +11,7 @@ interface CommandContextParams<
15
11
  /**
16
12
  * An options of target command
17
13
  */
18
- options: Options | undefined;
14
+ options: Options;
19
15
  /**
20
16
  * A values of target command
21
17
  */
@@ -47,4 +43,4 @@ declare function createCommandContext<
47
43
  Values = ArgValues<Options>
48
44
  >({ options, values, positionals, command, commandOptions, omitted }: CommandContextParams<Options, Values>): Promise<Readonly<CommandContext<Options, Values>>>;
49
45
 
50
- export { DEFAULT_LOCALE, createCommandContext };
46
+ export { createCommandContext };
package/lib/context.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_LOCALE, createCommandContext } from "./context-DmZAeiph.js";
2
- import "./utils-NHs5DuHk.js";
1
+ import { createCommandContext } from "./context-DYokJ5k3.js";
2
+ import "./utils-CU_LSsUg.js";
3
3
 
4
- export { DEFAULT_LOCALE, createCommandContext };
4
+ export { createCommandContext };
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ArgOptions } from 'args-tokens';
2
2
  export { ArgOptionSchema, ArgOptions, ArgValues } from 'args-tokens';
3
- import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-CxaX4FVV.js';
4
- export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment, h as CommandResource, i as CommandResourceFetcher, j as Commandable, L as LazyCommand } from './types.d-CxaX4FVV.js';
3
+ import { C as Command, c as CommandRunner, a as CommandOptions, T as TranslationAdapter, d as TranslationAdapterFactoryOptions } from './types.d-CX4RmDVT.js';
4
+ export { g as CommandBuiltinKeys, e as CommandBuiltinOptionsKeys, f as CommandBuiltinResourceKeys, b as CommandContext, h as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, l as Commandable, G as GenerateNamespacedKey, L as LazyCommand, k as TranslationAdapterFactory } from './types.d-CX4RmDVT.js';
5
5
 
6
6
  /**
7
7
  * Run the command
@@ -11,4 +11,14 @@ export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBu
11
11
  */
12
12
  declare function cli<Options extends ArgOptions = ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string | undefined>;
13
13
 
14
- export { Command, CommandOptions, CommandRunner, cli };
14
+ declare class DefaultTranslation implements TranslationAdapter {
15
+ #private;
16
+ options: TranslationAdapterFactoryOptions;
17
+ constructor(options: TranslationAdapterFactoryOptions);
18
+ getResource(locale: string): Record<string, string> | undefined;
19
+ setResource(locale: string, resource: Record<string, string>): void;
20
+ getMessage(locale: string, key: string): string | undefined;
21
+ translate(locale: string, key: string, _values?: Record<string, unknown>): string | undefined;
22
+ }
23
+
24
+ export { Command, CommandOptions, CommandRunner, DefaultTranslation, TranslationAdapter, TranslationAdapterFactoryOptions, cli };
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, createCommandContext } from "./context-DmZAeiph.js";
2
- import { create, log, resolveLazyCommand } from "./utils-NHs5DuHk.js";
3
- import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-v5Uq0km9.js";
1
+ import { DefaultTranslation, createCommandContext } from "./context-DYokJ5k3.js";
2
+ import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, log, resolveLazyCommand } from "./utils-CU_LSsUg.js";
3
+ import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-KLx2dW-W.js";
4
4
  import { parseArgs, resolveArgs } from "args-tokens";
5
5
 
6
6
  //#region src/cli.ts
@@ -45,7 +45,8 @@ function resolveArgOptions(options) {
45
45
  function resolveCommandOptions(options, entry) {
46
46
  const subCommands = new Map(options.subCommands);
47
47
  if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
48
- return Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
48
+ const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
49
+ return resolvedOptions;
49
50
  }
50
51
  function getSubCommand(tokens) {
51
52
  const firstToken = tokens[0];
@@ -92,4 +93,4 @@ async function resolveCommand(sub, entry, options) {
92
93
  }
93
94
 
94
95
  //#endregion
95
- export { cli };
96
+ export { DefaultTranslation, cli };
@@ -1,5 +1,5 @@
1
1
  import { ArgOptions } from 'args-tokens';
2
- import { b as CommandContext } from '../types.d-CxaX4FVV.js';
2
+ import { b as CommandContext } from '../types.d-CX4RmDVT.js';
3
3
 
4
4
  /**
5
5
  * Render the header
@@ -1,4 +1,4 @@
1
- import "../utils-NHs5DuHk.js";
2
- import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-v5Uq0km9.js";
1
+ import "../utils-CU_LSsUg.js";
2
+ import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-KLx2dW-W.js";
3
3
 
4
4
  export { renderHeader, renderUsage, renderValidationErrors };
@@ -1,4 +1,4 @@
1
- import { create } from "./utils-NHs5DuHk.js";
1
+ import { create, resolveBuiltInKey } from "./utils-CU_LSsUg.js";
2
2
 
3
3
  //#region src/renderer/header.ts
4
4
  function renderHeader(ctx) {
@@ -27,9 +27,8 @@ async function renderUsage(ctx) {
27
27
  */
28
28
  async function renderOptionsSection(ctx) {
29
29
  const messages = [];
30
- messages.push(`${ctx.translation("OPTIONS")}:`);
31
- const optionsPairs = getOptionsPairs(ctx);
32
- messages.push(await generateOptionsUsage(ctx, optionsPairs));
30
+ messages.push(`${ctx.translate(resolveBuiltInKey("OPTIONS"))}:`);
31
+ messages.push(await generateOptionsUsage(ctx, getOptionsPairs(ctx)));
33
32
  return messages;
34
33
  }
35
34
  /**
@@ -40,7 +39,7 @@ async function renderOptionsSection(ctx) {
40
39
  function renderExamplesSection(ctx) {
41
40
  const messages = [];
42
41
  const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
43
- messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
42
+ messages.push(`${ctx.translate(resolveBuiltInKey("EXAMPLES"))}:`, ...examples);
44
43
  return messages;
45
44
  }
46
45
  /**
@@ -49,12 +48,12 @@ function renderExamplesSection(ctx) {
49
48
  * @returns A rendered usage section
50
49
  */
51
50
  async function renderUsageSection(ctx) {
52
- const messages = [`${ctx.translation("USAGE")}:`];
51
+ const messages = [`${ctx.translate(resolveBuiltInKey("USAGE"))}:`];
53
52
  if (ctx.omitted) {
54
- const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
53
+ const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translate(resolveBuiltInKey("OPTIONS"))}>` : ""} `;
55
54
  messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
56
55
  if (await hasCommands(ctx)) {
57
- const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
56
+ const commandsUsage = `${resolveEntry(ctx)} <${ctx.translate(resolveBuiltInKey("COMMANDS"))}>`;
58
57
  messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
59
58
  }
60
59
  } else {
@@ -69,7 +68,7 @@ async function renderUsageSection(ctx) {
69
68
  * @returns A rendered commands section
70
69
  */
71
70
  async function renderCommandsSection(ctx) {
72
- const messages = [`${ctx.translation("COMMANDS")}:`];
71
+ const messages = [`${ctx.translate(resolveBuiltInKey("COMMANDS"))}:`];
73
72
  const loadedCommands = await ctx.loadCommands();
74
73
  const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
75
74
  const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
@@ -78,7 +77,7 @@ async function renderCommandsSection(ctx) {
78
77
  const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
79
78
  return `${command.padStart(ctx.env.leftMargin + command.length)} `;
80
79
  }));
81
- messages.push(...commandsStr, "", ctx.translation("FORMORE"));
80
+ messages.push(...commandsStr, "", ctx.translate(resolveBuiltInKey("FORMORE")));
82
81
  messages.push(...loadedCommands.map((cmd) => {
83
82
  const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
84
83
  return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
@@ -91,7 +90,7 @@ async function renderCommandsSection(ctx) {
91
90
  * @returns The entry command name
92
91
  */
93
92
  function resolveEntry(ctx) {
94
- return ctx.env.name || ctx.translation("COMMAND");
93
+ return ctx.env.name || ctx.translate(resolveBuiltInKey("COMMAND"));
95
94
  }
96
95
  /**
97
96
  * Resolve the sub command name
@@ -99,7 +98,7 @@ function resolveEntry(ctx) {
99
98
  * @returns The sub command name
100
99
  */
101
100
  function resolveSubCommand(ctx) {
102
- return ctx.name || ctx.translation("SUBCOMMAND");
101
+ return ctx.name || ctx.translate(resolveBuiltInKey("SUBCOMMAND"));
103
102
  }
104
103
  /**
105
104
  * Resolve the command description
@@ -107,7 +106,7 @@ function resolveSubCommand(ctx) {
107
106
  * @returns resolved command description
108
107
  */
109
108
  function resolveDescription(ctx) {
110
- return ctx.translation("description") || ctx.description || "";
109
+ return ctx.translate("description") || ctx.description || "";
111
110
  }
112
111
  /**
113
112
  * Check if the command has sub commands
@@ -148,7 +147,7 @@ function hasAllDefaultOptions(ctx) {
148
147
  * @returns Options symbols for usage
149
148
  */
150
149
  function generateOptionsSymbols(ctx) {
151
- return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
150
+ return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate("_:OPTIONS")}]` : `<${ctx.translate("_:OPTIONS")}>` : "";
152
151
  }
153
152
  /**
154
153
  * Get options pairs for usage
@@ -174,7 +173,7 @@ async function generateOptionsUsage(ctx, optionsPairs) {
174
173
  const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
175
174
  const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
176
175
  const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
177
- const rawDesc = ctx.translation(key);
176
+ const rawDesc = ctx.translate(key);
178
177
  const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
179
178
  const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
180
179
  const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
@@ -1,5 +1,11 @@
1
1
  import { ArgOptions, ArgValues } from 'args-tokens';
2
2
 
3
+ /**
4
+ * The default locale string, which format is BCP 47 language tag
5
+ */
6
+ declare const DEFAULT_LOCALE = "en-US";
7
+ declare const BUILT_IN_PREFIX = "_";
8
+ declare const BUILT_IN_KEY_SEPARATOR = ":";
3
9
  type CommonOptionType = {
4
10
  readonly help: {
5
11
  readonly type: "boolean"
@@ -12,19 +18,26 @@ type CommonOptionType = {
12
18
  };
13
19
  declare const COMMON_OPTIONS: CommonOptionType;
14
20
  declare const COMMAND_OPTIONS_DEFAULT: CommandOptions<ArgOptions>;
15
- declare const COMMAND_I18N_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "OPTIONS", "EXAMPLES", "FORMORE"];
21
+ declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "OPTIONS", "EXAMPLES", "FORMORE"];
16
22
 
17
- declare const __constants_COMMAND_I18N_RESOURCE_KEYS: typeof COMMAND_I18N_RESOURCE_KEYS;
23
+ declare const __constants_BUILT_IN_KEY_SEPARATOR: typeof BUILT_IN_KEY_SEPARATOR;
24
+ declare const __constants_BUILT_IN_PREFIX: typeof BUILT_IN_PREFIX;
25
+ declare const __constants_COMMAND_BUILTIN_RESOURCE_KEYS: typeof COMMAND_BUILTIN_RESOURCE_KEYS;
18
26
  declare const __constants_COMMAND_OPTIONS_DEFAULT: typeof COMMAND_OPTIONS_DEFAULT;
19
27
  declare const __constants_COMMON_OPTIONS: typeof COMMON_OPTIONS;
28
+ declare const __constants_DEFAULT_LOCALE: typeof DEFAULT_LOCALE;
20
29
  declare namespace __constants {
21
- export { __constants_COMMAND_I18N_RESOURCE_KEYS as COMMAND_I18N_RESOURCE_KEYS, __constants_COMMAND_OPTIONS_DEFAULT as COMMAND_OPTIONS_DEFAULT, __constants_COMMON_OPTIONS as COMMON_OPTIONS };
30
+ export { __constants_BUILT_IN_KEY_SEPARATOR as BUILT_IN_KEY_SEPARATOR, __constants_BUILT_IN_PREFIX as BUILT_IN_PREFIX, __constants_COMMAND_BUILTIN_RESOURCE_KEYS as COMMAND_BUILTIN_RESOURCE_KEYS, __constants_COMMAND_OPTIONS_DEFAULT as COMMAND_OPTIONS_DEFAULT, __constants_COMMON_OPTIONS as COMMON_OPTIONS, __constants_DEFAULT_LOCALE as DEFAULT_LOCALE };
22
31
  }
23
32
 
24
33
  /**
25
34
  * Define a promise type that can be await from T
26
35
  */
27
36
  type Awaitable<T> = T | Promise<T>;
37
+ type GenerateNamespacedKey<
38
+ Key extends string,
39
+ Prefixed extends string = typeof BUILT_IN_PREFIX
40
+ > = `${Prefixed}${typeof BUILT_IN_KEY_SEPARATOR}${Key}`;
28
41
  /**
29
42
  * Command i18n built-in options keys
30
43
  * @experimental
@@ -34,13 +47,13 @@ type CommandBuiltinOptionsKeys = keyof (typeof __constants)["COMMON_OPTIONS"];
34
47
  * Command i18n built-in resource keys
35
48
  * @experimental
36
49
  */
37
- type CommandBuiltinResourceKeys = (typeof __constants)["COMMAND_I18N_RESOURCE_KEYS"][number];
50
+ type CommandBuiltinResourceKeys = (typeof __constants)["COMMAND_BUILTIN_RESOURCE_KEYS"][number];
38
51
  /**
39
52
  * Command i18n built-in keys
40
- * @description The command i18n built-in keys are used to {@link CommandContext.translation | translate} function
53
+ * @description The command i18n built-in keys are used to {@link CommandContext.translate | translate} function
41
54
  * @experimental
42
55
  */
43
- type CommandBuiltinKeys = CommandBuiltinOptionsKeys | CommandBuiltinResourceKeys | "description" | "examples";
56
+ type CommandBuiltinKeys = GenerateNamespacedKey<CommandBuiltinOptionsKeys> | GenerateNamespacedKey<CommandBuiltinResourceKeys> | "description" | "examples";
44
57
  /**
45
58
  * Command environment
46
59
  */
@@ -155,6 +168,11 @@ interface CommandOptions<Options extends ArgOptions = ArgOptions> {
155
168
  * Render function the validation errors
156
169
  */
157
170
  renderValidationErrors?: ((ctx: Readonly<CommandContext<Options>>, error: AggregateError) => Promise<string>) | null;
171
+ /**
172
+ * Translation adapter factory
173
+ * @experimental
174
+ */
175
+ translationAdapterFactory?: TranslationAdapterFactory;
158
176
  }
159
177
  /**
160
178
  * Command context
@@ -187,7 +205,7 @@ interface CommandContext<
187
205
  * Command options, that is the options of the command that is executed
188
206
  * @description The command options is same {@link Command.options}
189
207
  */
190
- options: Options | undefined;
208
+ options: Options;
191
209
  /**
192
210
  * Command values, that is the values of the command that is executed
193
211
  * @description Resolve values with `resolveArgs` from command arguments and {@link Command.options}
@@ -214,15 +232,16 @@ interface CommandContext<
214
232
  */
215
233
  loadCommands: () => Promise<Command<Options>[]>;
216
234
  /**
217
- * Translation function
235
+ * Translate function
218
236
  * @param key the key to be translated
237
+ * @param values the values to be formatted
219
238
  * @returns A translated string
220
239
  * @experimental
221
240
  */
222
- translation: <
223
- T = CommandBuiltinKeys,
224
- Key = CommandBuiltinKeys | T
225
- >(key: Key) => string;
241
+ translate: <
242
+ T extends string = CommandBuiltinKeys,
243
+ Key = CommandBuiltinKeys | keyof Options | T
244
+ >(key: Key, values?: Record<string, unknown>) => string;
226
245
  }
227
246
  /**
228
247
  * Command usage
@@ -282,20 +301,18 @@ interface Command<Options extends ArgOptions = ArgOptions> {
282
301
  * Command resource
283
302
  * @experimental
284
303
  */
285
- interface CommandResource<Options extends ArgOptions = ArgOptions> {
304
+ type CommandResource<Options extends ArgOptions = ArgOptions> = {
286
305
  /**
287
306
  * Command description
288
307
  */
289
- description: string;
290
- /**
291
- * Options usage
292
- */
293
- options: { [Option in keyof Options] : string };
308
+ description: string
294
309
  /**
295
310
  * Examples usage
296
311
  */
297
- examples: string;
298
- }
312
+ examples: string
313
+ } & { [Option in keyof Options] : string } & {
314
+ [key: string]: string
315
+ };
299
316
  /**
300
317
  * Command resource fetcher
301
318
  * @param ctx A {@link CommandContext | command context}
@@ -304,6 +321,59 @@ interface CommandResource<Options extends ArgOptions = ArgOptions> {
304
321
  */
305
322
  type CommandResourceFetcher<Options extends ArgOptions = ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
306
323
  /**
324
+ * Translation adapter factory
325
+ */
326
+ type TranslationAdapterFactory = (options: TranslationAdapterFactoryOptions) => TranslationAdapter;
327
+ /**
328
+ * Translation adapter factory options
329
+ */
330
+ interface TranslationAdapterFactoryOptions {
331
+ /**
332
+ * A locale
333
+ */
334
+ locale: string;
335
+ /**
336
+ * A fallback locale
337
+ */
338
+ fallbackLocale: string;
339
+ }
340
+ /**
341
+ * Translation adapter
342
+ *
343
+ * @description
344
+ * This adapter is used to custom message formatter like {@link https://github.com/intlify/vue-i18n/blob/master/spec/syntax.ebnf | Intlify message format}, {@link https://github.com/tc39/proposal-intl-messageformat | `Intl.MessageFormat` (MF2)}, and etc.
345
+ * This adapter will support localization with your preferred message format
346
+ */
347
+ interface TranslationAdapter<MessageResource = string> {
348
+ /**
349
+ * Get a resource of locale
350
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
351
+ * @returns A resource of locale. if resource not found, return `undefined`
352
+ */
353
+ getResource(locale: string): Record<string, string> | undefined;
354
+ /**
355
+ * Set a resource of locale
356
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
357
+ * @param resource A resource of locale
358
+ */
359
+ setResource(locale: string, resource: Record<string, string>): void;
360
+ /**
361
+ * Get a message of locale
362
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
363
+ * @param key A key of message resource
364
+ * @returns A message of locale. if message not found, return `undefined`
365
+ */
366
+ getMessage(locale: string, key: string): MessageResource | undefined;
367
+ /**
368
+ * Translate a message
369
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
370
+ * @param key A key of message resource
371
+ * @param values A values to be resolved in the message
372
+ * @returns A translated message, if message is not translated, return `undefined`
373
+ */
374
+ translate(locale: string, key: string, values?: Record<string, unknown>): string | undefined;
375
+ }
376
+ /**
307
377
  * Command runner
308
378
  * @param ctx A {@link CommandContext | command context}
309
379
  */
@@ -318,4 +388,4 @@ type LazyCommand<Options extends ArgOptions = ArgOptions> = () => Awaitable<Comm
318
388
  */
319
389
  type Commandable<Options extends ArgOptions> = Command<Options> | LazyCommand<Options>;
320
390
 
321
- export type { Command as C, LazyCommand as L, CommandOptions as a, CommandContext as b, CommandRunner as c, CommandBuiltinOptionsKeys as d, CommandBuiltinResourceKeys as e, CommandBuiltinKeys as f, CommandEnvironment as g, CommandResource as h, CommandResourceFetcher as i, Commandable as j };
391
+ export type { Command as C, GenerateNamespacedKey as G, LazyCommand as L, TranslationAdapter as T, CommandOptions as a, CommandContext as b, CommandRunner as c, TranslationAdapterFactoryOptions as d, CommandBuiltinOptionsKeys as e, CommandBuiltinResourceKeys as f, CommandBuiltinKeys as g, CommandEnvironment as h, CommandResource as i, CommandResourceFetcher as j, TranslationAdapterFactory as k, Commandable as l };
@@ -0,0 +1,63 @@
1
+
2
+ //#region src/constants.ts
3
+ const DEFAULT_LOCALE = "en-US";
4
+ const BUILT_IN_PREFIX = "_";
5
+ const BUILT_IN_KEY_SEPARATOR = ":";
6
+ const COMMON_OPTIONS = {
7
+ help: {
8
+ type: "boolean",
9
+ short: "h"
10
+ },
11
+ version: {
12
+ type: "boolean",
13
+ short: "v"
14
+ }
15
+ };
16
+ const COMMAND_OPTIONS_DEFAULT = {
17
+ name: undefined,
18
+ description: undefined,
19
+ version: undefined,
20
+ cwd: undefined,
21
+ subCommands: undefined,
22
+ leftMargin: 2,
23
+ middleMargin: 10,
24
+ usageOptionType: false,
25
+ renderHeader: undefined,
26
+ renderUsage: undefined,
27
+ renderValidationErrors: undefined,
28
+ translationAdapterFactory: undefined
29
+ };
30
+
31
+ //#endregion
32
+ //#region src/utils.ts
33
+ async function resolveLazyCommand(cmd, name, entry = false) {
34
+ const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
35
+ if (resolved.name == null && name) resolved.name = name;
36
+ return deepFreeze(resolved);
37
+ }
38
+ function resolveBuiltInKey(key) {
39
+ return `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
40
+ }
41
+ function mapResourceWithBuiltinKey(resource) {
42
+ return Object.entries(resource).reduce((acc, [key, value]) => {
43
+ acc[resolveBuiltInKey(key)] = value;
44
+ return acc;
45
+ }, create());
46
+ }
47
+ function create(obj = null) {
48
+ return Object.create(obj);
49
+ }
50
+ function log(...args) {
51
+ console.log(...args);
52
+ }
53
+ function deepFreeze(obj) {
54
+ if (obj === null || typeof obj !== "object") return obj;
55
+ for (const key of Object.keys(obj)) {
56
+ const value = obj[key];
57
+ if (typeof value === "object" && value !== null) deepFreeze(value);
58
+ }
59
+ return Object.freeze(obj);
60
+ }
61
+
62
+ //#endregion
63
+ export { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveBuiltInKey, resolveLazyCommand };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gunshi",
3
3
  "description": "Modern javascript command-line library",
4
- "version": "0.6.2",
4
+ "version": "0.8.0",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -71,6 +71,7 @@
71
71
  },
72
72
  "devDependencies": {
73
73
  "@eslint/markdown": "^6.2.2",
74
+ "@intlify/core": "next",
74
75
  "@kazupon/eslint-config": "^0.22.0",
75
76
  "@kazupon/prettier-config": "^0.1.1",
76
77
  "@types/node": "^22.13.9",
@@ -87,6 +88,7 @@
87
88
  "jsr": "^0.13.4",
88
89
  "knip": "^5.45.0",
89
90
  "lint-staged": "^15.4.3",
91
+ "messageformat": "4.0.0-9",
90
92
  "pkg-pr-new": "^0.0.41",
91
93
  "prettier": "^3.5.3",
92
94
  "tsdown": "^0.6.4",
@@ -1,24 +0,0 @@
1
-
2
- //#region src/utils.ts
3
- async function resolveLazyCommand(cmd, name, entry = false) {
4
- const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
5
- if (resolved.name == null && name) resolved.name = name;
6
- return deepFreeze(resolved);
7
- }
8
- function create(obj = null) {
9
- return Object.create(obj);
10
- }
11
- function log(...args) {
12
- console.log(...args);
13
- }
14
- function deepFreeze(obj) {
15
- if (obj === null || typeof obj !== "object") return obj;
16
- for (const key of Object.keys(obj)) {
17
- const value = obj[key];
18
- if (typeof value === "object" && value !== null) deepFreeze(value);
19
- }
20
- return Object.freeze(obj);
21
- }
22
-
23
- //#endregion
24
- export { create, deepFreeze, log, resolveLazyCommand };