@gunshi/plugin-i18n 0.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 kazuya kawaguchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ 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, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,527 @@
1
+ # @gunshi/plugin-i18n
2
+
3
+ > internationalization (i18n) plugin for gunshi.
4
+
5
+ This plugin provides multi-language support for your CLI applications, allowing you to create commands that can display messages in different languages based on user locale.
6
+
7
+ ## 💿 Installation
8
+
9
+ ```sh
10
+ # npm
11
+ npm install --save @gunshi/plugin-i18n
12
+
13
+ # pnpm
14
+ pnpm add @gunshi/plugin-i18n
15
+
16
+ # yarn
17
+ yarn add @gunshi/plugin-i18n
18
+
19
+ # deno
20
+ deno add jsr:@gunshi/plugin-i18n
21
+
22
+ # bun
23
+ bun add @gunshi/plugin-i18n
24
+ ```
25
+
26
+ ## 🚀 Usage
27
+
28
+ ```ts
29
+ import { cli } from 'gunshi'
30
+ import i18n, { defineI18n } from '@gunshi/plugin-i18n'
31
+
32
+ /**
33
+ * You can define a command with `defineI18n`, which is compatible with the `define` function.
34
+ * This provides full type safety for i18n commands - TypeScript will suggest the 'resource' option
35
+ * and ensure your resource keys match the expected structure.
36
+ */
37
+
38
+ // Define a command
39
+ const greetCommand = defineI18n({
40
+ name: 'greet',
41
+ description: 'Greet someone',
42
+
43
+ args: {
44
+ name: {
45
+ type: 'string',
46
+ description: 'Name to greet'
47
+ }
48
+ },
49
+
50
+ // Define resource fetcher for translations
51
+ resource: async ctx => ({
52
+ description: 'Greet someone in their language',
53
+ examples: '$ your-cli --name yourname',
54
+ 'arg:name': "The person's name",
55
+ greeting: 'Hello, {$name}!'
56
+ }),
57
+
58
+ run: async ctx => {
59
+ const { name } = ctx.values
60
+ // Use translate function from context
61
+ console.log(ctx.extensions['g:i18n'].translate('greeting', { name }))
62
+ }
63
+ })
64
+
65
+ // Run CLI with i18n plugin
66
+ await cli(process.argv.slice(2), greetCommand, {
67
+ plugins: [
68
+ i18n({
69
+ locale: 'en-US' // or use process.env.LANG, navigator.language, etc.
70
+ })
71
+ ]
72
+ })
73
+ ```
74
+
75
+ ## ⚙️ Plugin Options
76
+
77
+ ### `locale`
78
+
79
+ - Type: `string | Intl.Locale`
80
+ - Default: `'en-US'`
81
+ - Description: The locale to use for translations. Can be a BCP 47 language tag string or an `Intl.Locale` object.
82
+
83
+ ### `resources`
84
+
85
+ - Type: `Record<string, Record<BuiltinResourceKeys, string>>`
86
+ - Default: `{}`
87
+ - Description:
88
+ - Built-in resource translations for different locales. Used for translating gunshi built-in level messages like "USAGE", "OPTIONS", "COMMANDS", etc.
89
+ - for details, see [Resource Key Naming Conventions](#resource-key-naming-conventions)
90
+
91
+ ### `translationAdapterFactory`
92
+
93
+ - Type: `(options: TranslationAdapterFactoryOptions) => TranslationAdapter`
94
+ - Default: `createTranslationAdapter`
95
+ - Description: Factory function to create a custom translation adapter. Useful for integrating with existing i18n libraries.
96
+
97
+ ## 🔗 Plugin Dependencies
98
+
99
+ The i18n plugin has an optional dependency on the `g:global` plugin:
100
+
101
+ - **Plugin ID**: `g:global` (optional)
102
+ - **Purpose**: Provides global options support for `--help` and `--version`
103
+ - **Effect**: When the globals plugin is present, the i18n plugin automatically sets up translations for the `help` and `version` built-in resources
104
+
105
+ This means:
106
+
107
+ - If you're using the globals plugin (which adds `--help` and `--version` options), the i18n plugin will automatically handle their translations
108
+ - The translations for "Display this help message" and "Display this version" will be available in your configured locale
109
+ - You can override these translations by providing your own `help` and `version` keys in your built-in resources
110
+
111
+ Example with globals plugin:
112
+
113
+ ```ts
114
+ import { cli } from 'gunshi'
115
+ import i18n from '@gunshi/plugin-i18n'
116
+ import globals from '@gunshi/plugin-global'
117
+
118
+ await cli(args, command, {
119
+ plugins: [
120
+ globals(), // Adds --help and --version options
121
+ i18n({
122
+ locale: 'ja-JP',
123
+ resources: {
124
+ 'ja-JP': {
125
+ help: 'ヘルプメッセージを表示', // Override help translation
126
+ version: 'バージョンを表示' // Override version translation
127
+ // ... other built-in translations
128
+ }
129
+ }
130
+ })
131
+ ]
132
+ })
133
+ ```
134
+
135
+ ## 🛠️ Helper Functions
136
+
137
+ ### `defineI18n`
138
+
139
+ Type-safe helper to define an i18n-aware command.
140
+
141
+ ```ts
142
+ import { defineI18n } from '@gunshi/plugin-i18n'
143
+
144
+ const command = defineI18n({
145
+ name: 'hello',
146
+ args: {
147
+ name: { type: 'string' }
148
+ },
149
+ resource: async ctx => ({
150
+ description: 'Say hello',
151
+ 'arg:name': 'Your name'
152
+ }),
153
+ run: async ctx => {
154
+ console.log(`Hello, ${ctx.values.name}!`)
155
+ }
156
+ })
157
+ ```
158
+
159
+ ### `withI18nResource`
160
+
161
+ Add i18n resource to an existing command. This helper is useful for extending an already defined command.
162
+
163
+ ```ts
164
+ import { define } from 'gunshi' // 'gunshi/definition', or '@gunshi/definition'
165
+ import { withI18nResource } from '@gunshi/plugin-i18n'
166
+
167
+ const basicCommand = define({
168
+ name: 'test',
169
+ args: {
170
+ target: {
171
+ type: 'string',
172
+ description: 'The test target file',
173
+ required: true
174
+ }
175
+ },
176
+ run: ctx => console.log(`test: ${ctx.values.target}`)
177
+ })
178
+
179
+ const i18nCommand = withI18nResource(basicCommand, async ctx => {
180
+ const resource = await import(
181
+ `./path/to/resources/test/${ctx.extensions['g:i18n'].locale.toString()}.json`,
182
+ { with: { type: 'json' } }
183
+ ).then(l => l.default || l)
184
+ return resource
185
+ })
186
+ ```
187
+
188
+ ## 🧩 Context Extensions
189
+
190
+ When using the i18n plugin, your command context is extended via `ctx.extensions['g:i18n']`.
191
+
192
+ <!-- eslint-disable markdown/no-missing-label-refs -->
193
+
194
+ > [!IMPORTANT]
195
+ > This plugin extension is namespaced in `CommandContext.extensions` using this plugin ID `g:i18n` by the gunshi plugin system.
196
+
197
+ <!-- eslint-enable markdown/no-missing-label-refs -->
198
+
199
+ Available extensions:
200
+
201
+ - `locale: Intl.Locale`: The current locale
202
+ - `translate<T>(key: T, values?: Record<string, unknown>): string`: Translation function
203
+
204
+ ## 📝 Resource Key Naming Conventions
205
+
206
+ When defining your localization resources (either directly in the `resource` function or in separate files), there are specific naming conventions to follow for the keys:
207
+
208
+ - **Command Description**: Use the key `description` for the main description of the command.
209
+ - **Examples**: Use the key `examples` for usage examples.
210
+ - **Argument and Option Descriptions**: Keys for the descriptions of both command arguments (positional args) and options **must** be prefixed with `arg:`. For example:
211
+ - For an argument named `file`: use `arg:file`
212
+ - For an option named `verbose`: use `arg:verbose`
213
+ - **Negatable Argument Descriptions**: For boolean options (e.g., `--verbose`), Gunshi automatically generates a description for the negatable version (e.g., `--no-verbose`) using the built-in `NEGATABLE` key (e.g., "Negatable of --verbose"). To provide a custom translation for a specific negatable option, use the pattern `arg:no-<optionName>`, for example, `arg:no-verbose`.
214
+ - **Custom Keys**: Any other keys you define for custom translation messages (like greetings, error messages, etc.) do not require a prefix and can be named freely (e.g., `informal_greeting`, `error_file_not_found`).
215
+ - **Built-in Keys**: Keys for built-in functionalities are handled by Gunshi's default locales. The complete list includes:
216
+ - `USAGE` - Usage section header
217
+ - `OPTIONS` - Options section header
218
+ - `ARGUMENTS` - Arguments section header
219
+ - `COMMANDS` - Commands section header
220
+ - `EXAMPLES` - Examples section header
221
+ - `FORMORE` - Footer text for additional help
222
+ - `NEGATABLE` - Prefix for negatable options (e.g., "Negatable of --verbose")
223
+ - `DEFAULT` - Prefix for default values (e.g., "default: 5")
224
+ - `CHOICES` - Prefix for available choices (e.g., "choices: red, green, blue")
225
+ - `help` - Description for the help option ("Display this help message")
226
+ - `version` - Description for the version option ("Display this version")
227
+
228
+ Internally, these keys are prefixed with `_:` (e.g., `_:USAGE`, `_:OPTIONS`), but you don't need to use this prefix directly. When overriding built-in translations in your resources, use the key names without the prefix (e.g., providing your own translation for `NEGATABLE`, not `_:NEGATABLE`).
229
+
230
+ Here's an example illustrating the convention:
231
+
232
+ ```ts
233
+ import { defineI18n } from '@gunshi/plugin-i18n'
234
+
235
+ const command = defineI18n({
236
+ name: 'my-command',
237
+ args: {
238
+ target: { type: 'string' },
239
+ verbose: { type: 'boolean' }
240
+ },
241
+ resource: async ctx => {
242
+ // Example for 'en-US' locale
243
+ return {
244
+ description: 'This is my command.', // No prefix
245
+ examples: '$ my-command --target file.txt', // No prefix
246
+ 'arg:target': 'The target file to process.', // 'arg:' prefix
247
+ 'arg:verbose': 'Enable verbose output.', // 'arg:' prefix
248
+ 'arg:no-verbose': 'Disable verbose logging specifically.', // Optional custom translation for the negatable option
249
+ processing_message: 'Processing target...' // No prefix
250
+ }
251
+ },
252
+ run: ctx => {
253
+ /* ... */
254
+ }
255
+ })
256
+ ```
257
+
258
+ Adhering to these conventions ensures that Gunshi correctly identifies and uses your translations for descriptions, help messages, and within your command's logic via `ctx.extensions['g:i18n'].translate()`.
259
+
260
+ <!-- eslint-disable markdown/no-missing-label-refs -->
261
+
262
+ > [!IMPORTANT]
263
+ > The resource object returned by the `resource` function (or loaded from external files like JSON) **must** be a flat key-value structure. Nested objects are not supported for translations using `ctx.extensions['g:i18n'].translate()`. Keep your translation keys simple and at the top level.
264
+
265
+ <!-- eslint-enable markdown/no-missing-label-refs -->
266
+
267
+ Good Flat structure:
268
+
269
+ ```json
270
+ {
271
+ "greeting": "Hello",
272
+ "farewell": "Goodbye"
273
+ }
274
+ ```
275
+
276
+ Bad Nested structure (won't work with `ctx.extensions['g:i18n'].translate('messages.greeting')`):
277
+
278
+ ```json
279
+ {
280
+ "messages": {
281
+ "greeting": "Hello",
282
+ "farewell": "Goodbye"
283
+ }
284
+ }
285
+ ```
286
+
287
+ ## 🔤 Translation Interpolation
288
+
289
+ The default translation adapter supports simple interpolation using `{$key}` syntax:
290
+
291
+ ```ts
292
+ // In your resource
293
+ const resource = {
294
+ welcome: 'Welcome, {$name}!',
295
+ 'items.count': 'You have {$count} items',
296
+ file_deleted: 'Deleted {$path}',
297
+ error_message: 'Error: {$error}'
298
+ }
299
+ // In your command
300
+ const { translate } = ctx.extensions['g:i18n']
301
+ translate('welcome', { name: 'John' }) // "Welcome, John!"
302
+ translate('items.count', { count: 5 }) // "You have 5 items"
303
+ translate('file_deleted', { path: '/tmp/file.txt' }) // "Deleted /tmp/file.txt"
304
+ translate('error_message', { error: 'File not found' }) // "Error: File not found"
305
+ ```
306
+
307
+ <!-- eslint-disable markdown/no-missing-label-refs -->
308
+
309
+ > [!NOTE]
310
+ > The default adapter only supports simple string interpolation. For more advanced features like pluralization or formatting, you can provide a custom translation adapter.
311
+
312
+ <!-- eslint-enable markdown/no-missing-label-refs -->
313
+
314
+ ## 🎨 Custom Translation Adapter
315
+
316
+ The default translation adapter provides basic string interpolation, but you might want to integrate with more powerful i18n libraries for features like:
317
+
318
+ - **Pluralization**: Different messages based on count values
319
+ - **Date/Time formatting**: Locale-aware formatting
320
+ - **Number formatting**: Currency, percentages, etc.
321
+ - **Complex interpolation**: Nested values, conditional messages
322
+ - **Message linking**: Reference other translations
323
+ - **Custom formatters**: Domain-specific formatting logic
324
+
325
+ ### Creating a Translation Adapter
326
+
327
+ To create a custom translation adapter, you need to implement the `TranslationAdapter` interface:
328
+
329
+ ```ts
330
+ interface TranslationAdapter {
331
+ // Get all resources for a locale
332
+ getResource(locale: string): Record<string, string> | undefined
333
+
334
+ // Set resources for a locale
335
+ setResource(locale: string, resource: Record<string, string>): void
336
+
337
+ // Get a single message
338
+ getMessage(locale: string, key: string): string | undefined
339
+
340
+ // Translate a message with interpolation
341
+ translate(locale: string, key: string, values?: Record<string, unknown>): string | undefined
342
+ }
343
+ ```
344
+
345
+ The adapter factory receives options with locale information:
346
+
347
+ ```ts
348
+ interface TranslationAdapterFactoryOptions {
349
+ locale: string // Current locale (BCP 47)
350
+ fallbackLocale: string // Fallback locale (default: 'en-US')
351
+ }
352
+ ```
353
+
354
+ ### How Translation Adapters Work
355
+
356
+ When you provide a custom translation adapter:
357
+
358
+ 1. **Initialization**: The i18n plugin calls your factory function with locale settings
359
+ 2. **Resource Loading**: When a command defines a `resource` function, the plugin:
360
+ - Calls the resource function to get translations
361
+ - Passes them to your adapter via `setResource(locale, resource)`
362
+ 3. **Translation**: When `translate()` is called:
363
+ - The plugin delegates to your adapter's `translate()` method
364
+ - Your adapter handles interpolation and formatting
365
+ - Returns the translated string or `undefined` if not found
366
+
367
+ ### Custom Case: Integrating with Intlify (Vue I18n Core)
368
+
369
+ [Intlify](https://github.com/intlify/core) is the core of Intlify (Vue I18n), but it can be used independently. Here's how to create a translation adapter for Intlify:
370
+
371
+ ```ts
372
+ import { cli } from 'gunshi'
373
+ import i18n, { defineI18n } from '@gunshi/plugin-i18n'
374
+ import {
375
+ createCoreContext,
376
+ getLocaleMessage,
377
+ NOT_RESOLVED,
378
+ setLocaleMessage,
379
+ translate as intlifyTranslate
380
+ } from '@intlify/core' // need to install `npm install --save @intlify/core@next`
381
+
382
+ // Create an Intlify translation adapter factory
383
+ function createIntlifyAdapterFactory(options) {
384
+ return new IntlifyTranslation(options)
385
+ }
386
+
387
+ class IntlifyTranslation {
388
+ #options
389
+ #context
390
+
391
+ constructor(options) {
392
+ this.#options = options
393
+
394
+ const { locale, fallbackLocale } = options
395
+ const messages = {
396
+ [locale]: {}
397
+ }
398
+
399
+ if (locale !== fallbackLocale) {
400
+ messages[fallbackLocale] = {}
401
+ }
402
+
403
+ // Create the Intlify core context
404
+ this.#context = createCoreContext({
405
+ locale,
406
+ fallbackLocale,
407
+ messages
408
+ })
409
+ }
410
+
411
+ getResource(locale) {
412
+ return getLocaleMessage(this.#context, locale)
413
+ }
414
+
415
+ setResource(locale, resource) {
416
+ setLocaleMessage(this.#context, locale, resource)
417
+ }
418
+
419
+ getMessage(locale, key) {
420
+ const resource = this.getResource(locale)
421
+ if (resource) {
422
+ return resource[key]
423
+ }
424
+ return
425
+ }
426
+
427
+ translate(locale, key, values = {}) {
428
+ // Check if the message exists in the specified locale or fallback locale
429
+ const message =
430
+ this.getMessage(locale, key) || this.getMessage(this.#options.fallbackLocale, key)
431
+ if (message === undefined) {
432
+ return
433
+ }
434
+
435
+ // Use Intlify's translate function
436
+ const result = intlifyTranslate(this.#context, key, values)
437
+ return typeof result === 'number' && result === NOT_RESOLVED ? undefined : result
438
+ }
439
+ }
440
+
441
+ // Define your command
442
+ const command = defineI18n({
443
+ name: 'greeter',
444
+
445
+ args: {
446
+ name: {
447
+ type: 'string',
448
+ short: 'n'
449
+ },
450
+ count: {
451
+ type: 'number',
452
+ short: 'c',
453
+ default: 1
454
+ }
455
+ },
456
+
457
+ // Define a resource fetcher with Intlify syntax
458
+ resource: async ctx => {
459
+ const locale = ctx.extensions['g:i18n'].locale.toString()
460
+
461
+ if (locale === 'ja-JP') {
462
+ return {
463
+ description: '挨拶アプリケーション',
464
+ 'arg:name': '挨拶する相手の名前',
465
+ 'arg:count': '挨拶の回数',
466
+ greeting: 'こんにちは、{name}さん!',
467
+ greeting_plural: 'こんにちは、{name}さん!({count}回目)'
468
+ }
469
+ }
470
+
471
+ return {
472
+ description: 'Greeting application',
473
+ 'arg:name': 'Name to greet',
474
+ 'arg:count': 'Number of greetings',
475
+ greeting: 'Hello, {name}!',
476
+ greeting_plural: 'Hello, {name}! (greeting #{count})'
477
+ }
478
+ },
479
+
480
+ run: ctx => {
481
+ const { name = 'World', count } = ctx.values
482
+ const { translate } = ctx.extensions['g:i18n']
483
+
484
+ // Use the translation function with Intlify
485
+ const key = count > 1 ? 'greeting_plural' : 'greeting'
486
+ const message = translate(key, { name, count })
487
+
488
+ console.log(message)
489
+ }
490
+ })
491
+
492
+ // Run the command with the Intlify translation adapter
493
+ await cli(process.argv.slice(2), command, {
494
+ name: 'intlify-example',
495
+ version: '1.0.0',
496
+ plugins: [
497
+ i18n({
498
+ locale: process.env.MY_LOCALE || 'en-US',
499
+ translationAdapterFactory: createIntlifyAdapterFactory
500
+ })
501
+ ]
502
+ })
503
+ ```
504
+
505
+ With Intlify, you get advanced features like:
506
+
507
+ - Named interpolation: `{name}` instead of `{$name}`
508
+ - Pluralization support
509
+ - Linked messages
510
+ - HTML formatting
511
+ - Custom modifiers
512
+ - And more
513
+
514
+ <!-- eslint-disable markdown/no-missing-label-refs -->
515
+
516
+ > [!TIP]
517
+ > Intlify uses `{name}` syntax for interpolation (without the `$` prefix), which is different from Gunshi's default adapter that uses `{$name}`.
518
+
519
+ <!-- eslint-enable markdown/no-missing-label-refs -->
520
+
521
+ ## 📚 API References
522
+
523
+ See the [API References](./docs/index.md)
524
+
525
+ ## ©️ License
526
+
527
+ [MIT](http://opensource.org/licenses/MIT)
package/lib/index.d.ts ADDED
@@ -0,0 +1,356 @@
1
+ import { Awaitable, Command, CommandContext, DefaultGunshiParams, ExtractArgs, GunshiParams, GunshiParamsConstraint, NormalizeToGunshiParams, PluginWithExtension } from "@gunshi/plugin";
2
+
3
+ //#region rolldown:runtime
4
+
5
+ //#endregion
6
+ //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/resolver-U72Jg6Ll.d.ts
7
+ //#region src/resolver.d.ts
8
+
9
+ /**
10
+ * An argument schema
11
+ * This schema is similar to the schema of the `node:utils`.
12
+ * difference is that:
13
+ * - `required` property and `description` property are added
14
+ * - `type` is not only 'string' and 'boolean', but also 'number', 'enum', 'positional', 'custom' too.
15
+ * - `default` property type, not support multiple types
16
+ */
17
+ interface ArgSchema {
18
+ /**
19
+ * Type of argument.
20
+ */
21
+ type: "string" | "boolean" | "number" | "enum" | "positional" | "custom";
22
+ /**
23
+ * A single character alias for the argument.
24
+ */
25
+ short?: string;
26
+ /**
27
+ * A description of the argument.
28
+ */
29
+ description?: string;
30
+ /**
31
+ * Whether the argument is required or not.
32
+ */
33
+ required?: true;
34
+ /**
35
+ * Whether the argument allow multiple values or not.
36
+ */
37
+ multiple?: true;
38
+ /**
39
+ * Whether the negatable option for `boolean` type
40
+ */
41
+ negatable?: boolean;
42
+ /**
43
+ * The allowed values of the argument, and string only. This property is only used when the type is 'enum'.
44
+ */
45
+ choices?: string[] | readonly string[];
46
+ /**
47
+ * The default value of the argument.
48
+ * if the type is 'enum', the default value must be one of the allowed values.
49
+ */
50
+ default?: string | boolean | number;
51
+ /**
52
+ * Whether to convert the argument name to kebab-case.
53
+ */
54
+ toKebab?: true;
55
+ /**
56
+ * A function to parse the value of the argument. if the type is 'custom', this function is required.
57
+ * If argument value will be invalid, this function have to throw an error.
58
+ * @param value
59
+ * @returns Parsed value
60
+ * @throws An Error, If the value is invalid. Error type should be `Error` or extends it
61
+ */
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ parse?: (value: string) => any;
64
+ }
65
+ /**
66
+ * An object that contains {@link ArgSchema | argument schema}.
67
+ */
68
+ interface Args {
69
+ [option: string]: ArgSchema;
70
+ } //#endregion
71
+ //#region ../shared/src/constants.d.ts
72
+
73
+ /**
74
+ * An object that contains the values of the arguments.
75
+ */
76
+ declare namespace constants_d_exports {
77
+ export { ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, COMMAND_BUILTIN_RESOURCE_KEYS, COMMON_ARGS, PLUGIN_PREFIX };
78
+ }
79
+ /**
80
+ * @author kazuya kawaguchi (a.k.a. kazupon)
81
+ * @license MIT
82
+ */
83
+ declare const BUILT_IN_PREFIX = "_";
84
+ declare const PLUGIN_PREFIX = "g";
85
+ declare const ARG_PREFIX = "arg";
86
+ declare const BUILT_IN_KEY_SEPARATOR = ":";
87
+ declare const BUILD_IN_PREFIX_AND_KEY_SEPARATOR: string;
88
+ declare const ARG_PREFIX_AND_KEY_SEPARATOR: string;
89
+ declare const ARG_NEGATABLE_PREFIX = "no-";
90
+ type CommonArgType = {
91
+ readonly help: {
92
+ readonly type: 'boolean';
93
+ readonly short: 'h';
94
+ readonly description: string;
95
+ };
96
+ readonly version: {
97
+ readonly type: 'boolean';
98
+ readonly short: 'v';
99
+ readonly description: string;
100
+ };
101
+ };
102
+ declare const COMMON_ARGS: CommonArgType;
103
+ declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES"];
104
+
105
+ //#endregion
106
+ //#region ../shared/src/types.d.ts
107
+ type RemoveIndexSignature<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] };
108
+ /**
109
+ * Remove index signature from object or record type.
110
+ */
111
+ type RemovedIndex<T> = RemoveIndexSignature<{ [K in keyof T]: T[K] }>;
112
+ type KeyOfArgs<A extends Args> = keyof A | { [K in keyof A]: A[K]['type'] extends 'boolean' ? A[K]['negatable'] extends true ? `no-${Extract<K, string>}` : never : never }[keyof A];
113
+ /**
114
+ * Generate a namespaced key.
115
+ */
116
+ type GenerateNamespacedKey<Key extends string, Prefixed extends string = typeof BUILT_IN_PREFIX> = `${Prefixed}${typeof BUILT_IN_KEY_SEPARATOR}${Key}`;
117
+ /**
118
+ * Command i18n built-in arguments keys.
119
+ */
120
+ type CommandBuiltinArgsKeys = keyof (typeof constants_d_exports)['COMMON_ARGS'];
121
+ /**
122
+ * Command i18n built-in resource keys.
123
+ */
124
+ type CommandBuiltinResourceKeys = (typeof constants_d_exports)['COMMAND_BUILTIN_RESOURCE_KEYS'][number];
125
+ /**
126
+ * i18n built-in resource keys.
127
+ */
128
+ type BuiltinResourceKeys = CommandBuiltinArgsKeys | CommandBuiltinResourceKeys;
129
+ /**
130
+ * Command i18n built-in keys.
131
+ * The command i18n built-in keys are used by the i18n plugin for translation.
132
+ */
133
+ type CommandBuiltinKeys = GenerateNamespacedKey<BuiltinResourceKeys> | 'description' | 'examples';
134
+ /**
135
+ * Command i18n option keys.
136
+ * The command i18n option keys are used by the i18n plugin for translation.
137
+ */
138
+ type CommandArgKeys<A extends Args> = GenerateNamespacedKey<KeyOfArgs<RemovedIndex<A>>, typeof ARG_PREFIX>;
139
+
140
+ //#endregion
141
+ //#region src/types.d.ts
142
+ /**
143
+ * The unique identifier for the i18n plugin.
144
+ */
145
+ declare const pluginId: GenerateNamespacedKey<'i18n', typeof PLUGIN_PREFIX>;
146
+ /**
147
+ * Type representing the unique identifier for i18n plugin.
148
+ */
149
+ type PluginId = typeof pluginId;
150
+ /**
151
+ * Extended command context which provides utilities via i18n plugin.
152
+ * These utilities are available via `CommandContext.extensions['g:i18n']`.
153
+ */
154
+ interface I18nCommandContext<G extends GunshiParams<any> = DefaultGunshiParams> {
155
+ /**
156
+ * Command locale
157
+ */
158
+ locale: string | Intl.Locale;
159
+ /**
160
+ * Translate a message
161
+ * @param key Translation key
162
+ * @param values Values to interpolate
163
+ * @returns Translated message. If the key is not found:
164
+ * - For custom keys: returns an empty string ('')
165
+ * - For built-in keys (prefixed with '_:'): returns the key itself
166
+ */
167
+ translate: <T extends string = CommandBuiltinKeys, O = CommandArgKeys<G['args']>, K = CommandBuiltinKeys | O | T>(key: K, values?: Record<string, unknown>) => string;
168
+ }
169
+ /**
170
+ * i18n plugin options
171
+ */
172
+ interface I18nPluginOptions {
173
+ /**
174
+ * Locale to use for translations
175
+ */
176
+ locale?: string | Intl.Locale;
177
+ /**
178
+ * Translation adapter factory
179
+ */
180
+ translationAdapterFactory?: TranslationAdapterFactory;
181
+ /**
182
+ * Built-in localizable resources
183
+ */
184
+ resources?: Record<string, Record<BuiltinResourceKeys, string>>;
185
+ }
186
+ /**
187
+ * Translation adapter factory.
188
+ */
189
+ type TranslationAdapterFactory = (options: TranslationAdapterFactoryOptions) => TranslationAdapter;
190
+ /**
191
+ * Translation adapter factory options.
192
+ */
193
+ interface TranslationAdapterFactoryOptions {
194
+ /**
195
+ * A locale (BCP 47 language tag).
196
+ */
197
+ locale: string;
198
+ /**
199
+ * A fallback locale.
200
+ * @default DEFAULT_LOCALE ('en-US')
201
+ */
202
+ fallbackLocale: string;
203
+ }
204
+ /**
205
+ * Translation adapter.
206
+ * 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.
207
+ * This adapter will support localization with your preferred message format.
208
+ */
209
+ interface TranslationAdapter<MessageResource = string> {
210
+ /**
211
+ * Get a resource of locale.
212
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
213
+ * @returns A resource of locale. if resource not found, return `undefined`.
214
+ */
215
+ getResource(locale: string): Record<string, string> | undefined;
216
+ /**
217
+ * Set a resource of locale.
218
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
219
+ * @param resource A resource of locale
220
+ */
221
+ setResource(locale: string, resource: Record<string, string>): void;
222
+ /**
223
+ * Get a message of locale.
224
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
225
+ * @param key A key of message resource
226
+ * @returns A message of locale. if message not found, return `undefined`.
227
+ */
228
+ getMessage(locale: string, key: string): MessageResource | undefined;
229
+ /**
230
+ * Translate a message.
231
+ * @param locale A Locale at the time of command execution. That is Unicord locale ID (BCP 47)
232
+ * @param key A key of message resource
233
+ * @param values A values to be resolved in the message
234
+ * @returns A translated message, if message is not translated, return `undefined`.
235
+ */
236
+ translate(locale: string, key: string, values?: Record<string, unknown>): string | undefined;
237
+ }
238
+ /**
239
+ * Command resource type for i18n plugin.
240
+ */
241
+ type CommandResource<G extends GunshiParamsConstraint = DefaultGunshiParams> = {
242
+ /**
243
+ * Command description.
244
+ */
245
+ description: string;
246
+ /**
247
+ * Examples usage.
248
+ */
249
+ examples: string | CommandExamplesFetcher<NormalizeToGunshiParams<G>>;
250
+ } & { [Arg in GenerateNamespacedKey<KeyOfArgs<RemovedIndex<ExtractArgs<G>>>, typeof ARG_PREFIX>]: string } & {
251
+ [key: string]: string;
252
+ };
253
+ /**
254
+ * Command examples fetcher.
255
+ * @param ctx A {@link CommandContext | command context}
256
+ * @returns A fetched command examples.
257
+ */
258
+ type CommandExamplesFetcher<G extends GunshiParamsConstraint = DefaultGunshiParams> = (ctx: Readonly<CommandContext<G>>) => Awaitable<string>;
259
+ /**
260
+ * Command resource fetcher.
261
+ * @param ctx A {@link CommandContext | command context}
262
+ * @returns A fetched {@link CommandResource | command resource}.
263
+ */
264
+ type CommandResourceFetcher<G extends GunshiParamsConstraint = DefaultGunshiParams> = (ctx: Readonly<CommandContext<G>>) => Awaitable<CommandResource<G>>;
265
+ /**
266
+ * I18n-aware command interface that extends the base Command with resource support
267
+ */
268
+ interface I18nCommand<G extends GunshiParamsConstraint = DefaultGunshiParams> extends Command<G> {
269
+ /**
270
+ * Command resource fetcher for i18n support.
271
+ * This property is specific to i18n-enabled commands.
272
+ */
273
+ resource?: CommandResourceFetcher<G>;
274
+ }
275
+
276
+ //#endregion
277
+ //#region src/helpers.d.ts
278
+ /**
279
+ * Define an i18n-aware command with type safety
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * import { defineI18n } from '@gunshi/plugin-i18n'
284
+ *
285
+ * const greetCommand = defineI18n({
286
+ * name: 'greet',
287
+ * args: {
288
+ * name: { type: 'string', description: 'Name to greet' }
289
+ * },
290
+ * resource: async (ctx) => ({
291
+ * description: 'Greet someone',
292
+ * 'arg:name': 'The name to greet'
293
+ * }),
294
+ * run: async (ctx) => {
295
+ * console.log(`Hello, ${ctx.values.name}!`)
296
+ * }
297
+ * })
298
+ * ```
299
+ */
300
+ declare function defineI18n<G extends GunshiParamsConstraint = DefaultGunshiParams>(command: Command<G> & {
301
+ resource?: CommandResourceFetcher<G>;
302
+ }): I18nCommand<G>;
303
+ /**
304
+ * Add i18n resource to an existing command
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * import { define } from '@gunshi/definition'
309
+ * import { withI18nResource } from '@gunshi/plugin-i18n'
310
+ *
311
+ * const myCommand = define({
312
+ * name: 'myCommand',
313
+ * args: {
314
+ * input: { type: 'string', description: 'Input value' }
315
+ * },
316
+ * run: async (ctx) => {
317
+ * console.log(`Input: ${ctx.values.input}`)
318
+ * }
319
+ * })
320
+ *
321
+ * const i18nCommand = withI18nResource(basicCommand, async ctx => {
322
+ * const resource = await import(
323
+ * `./path/to/resources/test/${ctx.extensions['g:i18n'].locale.toString()}.json`,
324
+ * { with: { type: 'json' } }
325
+ * ).then(l => l.default || l)
326
+ * return resource
327
+ * })
328
+ * ```
329
+ */
330
+ declare function withI18nResource<G extends GunshiParamsConstraint>(command: Command<G>, resource: CommandResourceFetcher<G>): I18nCommand<G>;
331
+
332
+ //#endregion
333
+ //#region src/translation.d.ts
334
+ declare function createTranslationAdapter(options: TranslationAdapterFactoryOptions): TranslationAdapter;
335
+ declare class DefaultTranslation implements TranslationAdapter {
336
+ #private;
337
+ constructor(options: TranslationAdapterFactoryOptions);
338
+ getResource(locale: string): Record<string, string> | undefined;
339
+ setResource(locale: string, resource: Record<string, string>): void;
340
+ getMessage(locale: string, key: string): string | undefined;
341
+ translate(locale: string, key: string, values?: Record<string, unknown>): string | undefined;
342
+ }
343
+
344
+ //#endregion
345
+ //#region src/index.d.ts
346
+ /**
347
+ * The default locale string, which format is BCP 47 language tag.
348
+ */
349
+ declare const DEFAULT_LOCALE = "en-US";
350
+ /**
351
+ * i18n plugin
352
+ */
353
+ declare function i18n(options?: I18nPluginOptions): PluginWithExtension<Promise<I18nCommandContext<DefaultGunshiParams>>>;
354
+
355
+ //#endregion
356
+ export { CommandExamplesFetcher, CommandResource, CommandResourceFetcher, DEFAULT_LOCALE, DefaultTranslation, I18nCommand, I18nCommandContext, I18nPluginOptions, PluginId, TranslationAdapter, TranslationAdapterFactory, TranslationAdapterFactoryOptions, createTranslationAdapter, i18n as default, defineI18n, pluginId, withI18nResource };
package/lib/index.js ADDED
@@ -0,0 +1,261 @@
1
+ import { plugin } from "@gunshi/plugin";
2
+
3
+ //#region ../shared/src/constants.ts
4
+ /**
5
+ * @author kazuya kawaguchi (a.k.a. kazupon)
6
+ * @license MIT
7
+ */
8
+ const BUILT_IN_PREFIX = "_";
9
+ const PLUGIN_PREFIX = "g";
10
+ const ARG_PREFIX = "arg";
11
+ const BUILT_IN_KEY_SEPARATOR = ":";
12
+ const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
13
+ const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
14
+
15
+ //#endregion
16
+ //#region ../resources/locales/en-US.json
17
+ var COMMAND = "COMMAND";
18
+ var COMMANDS = "COMMANDS";
19
+ var SUBCOMMAND = "SUBCOMMAND";
20
+ var USAGE = "USAGE";
21
+ var ARGUMENTS = "ARGUMENTS";
22
+ var OPTIONS = "OPTIONS";
23
+ var EXAMPLES = "EXAMPLES";
24
+ var FORMORE = "For more info, run any command with the `--help` flag";
25
+ var NEGATABLE = "Negatable of";
26
+ var DEFAULT = "default";
27
+ var CHOICES = "choices";
28
+ var help = "Display this help message";
29
+ var version = "Display this version";
30
+ var en_US_default = {
31
+ COMMAND,
32
+ COMMANDS,
33
+ SUBCOMMAND,
34
+ USAGE,
35
+ ARGUMENTS,
36
+ OPTIONS,
37
+ EXAMPLES,
38
+ FORMORE,
39
+ NEGATABLE,
40
+ DEFAULT,
41
+ CHOICES,
42
+ help,
43
+ version
44
+ };
45
+
46
+ //#endregion
47
+ //#region ../shared/src/utils.ts
48
+ function resolveBuiltInKey(key) {
49
+ return `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
50
+ }
51
+ function resolveArgKey(key) {
52
+ return `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
53
+ }
54
+ function namespacedId(id) {
55
+ return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
56
+ }
57
+
58
+ //#endregion
59
+ //#region src/translation.ts
60
+ function createTranslationAdapter(options) {
61
+ return new DefaultTranslation(options);
62
+ }
63
+ var DefaultTranslation = class {
64
+ #resources = new Map();
65
+ #options;
66
+ constructor(options) {
67
+ this.#options = options;
68
+ this.#resources.set(options.locale, Object.create(null));
69
+ if (options.locale !== options.fallbackLocale) this.#resources.set(options.fallbackLocale, Object.create(null));
70
+ }
71
+ getResource(locale) {
72
+ return this.#resources.get(locale);
73
+ }
74
+ setResource(locale, resource) {
75
+ this.#resources.set(locale, resource);
76
+ }
77
+ getMessage(locale, key) {
78
+ const resource = this.getResource(locale);
79
+ if (resource) return resource[key];
80
+ return void 0;
81
+ }
82
+ translate(locale, key, values = Object.create(null)) {
83
+ let message = this.getMessage(locale, key);
84
+ if (message === void 0 && locale !== this.#options.fallbackLocale) message = this.getMessage(this.#options.fallbackLocale, key);
85
+ if (message === void 0) return;
86
+ return message.replaceAll(/\{\$(\w+)\}/g, (_, name) => {
87
+ return values[name] == null ? "" : values[name].toString();
88
+ });
89
+ }
90
+ };
91
+
92
+ //#endregion
93
+ //#region src/types.ts
94
+ /**
95
+ * The unique identifier for the i18n plugin.
96
+ */
97
+ const pluginId = namespacedId("i18n");
98
+
99
+ //#endregion
100
+ //#region src/helpers.ts
101
+ /**
102
+ * Define an i18n-aware command with type safety
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { defineI18n } from '@gunshi/plugin-i18n'
107
+ *
108
+ * const greetCommand = defineI18n({
109
+ * name: 'greet',
110
+ * args: {
111
+ * name: { type: 'string', description: 'Name to greet' }
112
+ * },
113
+ * resource: async (ctx) => ({
114
+ * description: 'Greet someone',
115
+ * 'arg:name': 'The name to greet'
116
+ * }),
117
+ * run: async (ctx) => {
118
+ * console.log(`Hello, ${ctx.values.name}!`)
119
+ * }
120
+ * })
121
+ * ```
122
+ */
123
+ function defineI18n(command) {
124
+ return command;
125
+ }
126
+ /**
127
+ * Add i18n resource to an existing command
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { define } from '@gunshi/definition'
132
+ * import { withI18nResource } from '@gunshi/plugin-i18n'
133
+ *
134
+ * const myCommand = define({
135
+ * name: 'myCommand',
136
+ * args: {
137
+ * input: { type: 'string', description: 'Input value' }
138
+ * },
139
+ * run: async (ctx) => {
140
+ * console.log(`Input: ${ctx.values.input}`)
141
+ * }
142
+ * })
143
+ *
144
+ * const i18nCommand = withI18nResource(basicCommand, async ctx => {
145
+ * const resource = await import(
146
+ * `./path/to/resources/test/${ctx.extensions['g:i18n'].locale.toString()}.json`,
147
+ * { with: { type: 'json' } }
148
+ * ).then(l => l.default || l)
149
+ * return resource
150
+ * })
151
+ * ```
152
+ */
153
+ function withI18nResource(command, resource) {
154
+ return {
155
+ ...command,
156
+ resource
157
+ };
158
+ }
159
+
160
+ //#endregion
161
+ //#region src/index.ts
162
+ /**
163
+ * The default locale string, which format is BCP 47 language tag.
164
+ */
165
+ const DEFAULT_LOCALE = "en-US";
166
+ const BUILT_IN_PREFIX_CODE = BUILT_IN_PREFIX.codePointAt(0);
167
+ /**
168
+ * i18n plugin
169
+ */
170
+ function i18n(options = {}) {
171
+ const locale = toLocale(options.locale);
172
+ const localeStr = locale.toString();
173
+ const resources = options.resources || Object.create(null);
174
+ const translationAdapterFactory = options.translationAdapterFactory || createTranslationAdapter;
175
+ const adapter = translationAdapterFactory({
176
+ locale: localeStr,
177
+ fallbackLocale: DEFAULT_LOCALE
178
+ });
179
+ const localeBuiltinResources = new Map();
180
+ let builtInLoadedResources;
181
+ return plugin({
182
+ id: pluginId,
183
+ name: "internationalization",
184
+ dependencies: [{
185
+ id: namespacedId("global"),
186
+ optional: true
187
+ }],
188
+ extension: async () => {
189
+ function translate(key, values = Object.create(null)) {
190
+ const strKey = key;
191
+ if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) {
192
+ const resource = localeBuiltinResources.get(localeStr) || localeBuiltinResources.get(DEFAULT_LOCALE);
193
+ return resource[strKey] || strKey;
194
+ } else return adapter.translate(localeStr, strKey, values) || "";
195
+ }
196
+ function getResource(locale$1) {
197
+ const targetLocale = toLocale(locale$1);
198
+ const targetLocaleStr = targetLocale.toString();
199
+ return localeBuiltinResources.get(targetLocaleStr);
200
+ }
201
+ function setResource(locale$1, resource) {
202
+ const targetLocale = toLocale(locale$1);
203
+ const targetLocaleStr = targetLocale.toString();
204
+ if (localeBuiltinResources.has(targetLocaleStr)) return;
205
+ localeBuiltinResources.set(targetLocale.toString(), mapResourceWithBuiltinKey(resource));
206
+ }
207
+ setResource(DEFAULT_LOCALE, en_US_default);
208
+ for (const [locale$1, resource] of Object.entries(resources)) setResource(locale$1, resource);
209
+ builtInLoadedResources = getResource(locale);
210
+ return {
211
+ locale,
212
+ translate
213
+ };
214
+ },
215
+ onExtension: async (ctx, cmd) => {
216
+ /**
217
+ * load command resources, after the command context is extended
218
+ */
219
+ const loadedOptionsResources = Object.entries(ctx.args).map(([key, schema]) => [key, schema.description || ""]);
220
+ const defaultCommandResource = loadedOptionsResources.reduce((res, [key, value]) => {
221
+ res[resolveArgKey(key)] = value;
222
+ return res;
223
+ }, Object.create(null));
224
+ defaultCommandResource.description = cmd.description || "";
225
+ defaultCommandResource.examples = typeof cmd.examples === "string" ? cmd.examples : typeof cmd.examples === "function" ? await cmd.examples(ctx) : "";
226
+ adapter.setResource(DEFAULT_LOCALE, defaultCommandResource);
227
+ const originalResource = await loadCommandResource(ctx, cmd);
228
+ if (originalResource) {
229
+ const resource = Object.assign(Object.create(null), originalResource, { examples: typeof originalResource.examples === "string" ? originalResource.examples : typeof originalResource.examples === "function" ? await originalResource.examples(ctx) : "" });
230
+ if (builtInLoadedResources) {
231
+ resource.help = builtInLoadedResources.help;
232
+ resource.version = builtInLoadedResources.version;
233
+ }
234
+ adapter.setResource(localeStr, resource);
235
+ }
236
+ }
237
+ });
238
+ }
239
+ function toLocale(locale) {
240
+ return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE);
241
+ }
242
+ async function loadCommandResource(ctx, command) {
243
+ if (!hasI18nResource(command)) return void 0;
244
+ let resource;
245
+ try {
246
+ resource = await command.resource(ctx);
247
+ } catch {}
248
+ return resource;
249
+ }
250
+ function mapResourceWithBuiltinKey(resource) {
251
+ return Object.entries(resource).reduce((acc, [key, value]) => {
252
+ acc[resolveBuiltInKey(key)] = value;
253
+ return acc;
254
+ }, Object.create(null));
255
+ }
256
+ function hasI18nResource(command) {
257
+ return "resource" in command && typeof command.resource === "function";
258
+ }
259
+
260
+ //#endregion
261
+ export { DEFAULT_LOCALE, DefaultTranslation, createTranslationAdapter, i18n as default, defineI18n, pluginId, withI18nResource };
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@gunshi/plugin-i18n",
3
+ "description": "internationalization plugin for gunshi",
4
+ "version": "0.26.3",
5
+ "author": {
6
+ "name": "kazuya kawaguchi",
7
+ "email": "kawakazu80@gmail.com"
8
+ },
9
+ "license": "MIT",
10
+ "funding": "https://github.com/sponsors/kazupon",
11
+ "bugs": {
12
+ "url": "https://github.com/kazupon/gunshi/issues"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/kazupon/gunshi.git",
17
+ "directory": "packages/plugin-i18n"
18
+ },
19
+ "keywords": [
20
+ "gunshi",
21
+ "i18n",
22
+ "internationalization",
23
+ "plugin",
24
+ "cli"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "engines": {
30
+ "node": ">= 20"
31
+ },
32
+ "type": "module",
33
+ "files": [
34
+ "lib"
35
+ ],
36
+ "module": "lib/index.js",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./lib/index.d.ts",
40
+ "import": "./lib/index.js",
41
+ "require": "./lib/index.js",
42
+ "default": "./lib/index.js"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "types": "lib/index.d.ts",
47
+ "typesVersions": {
48
+ "*": {
49
+ "*": [
50
+ "./lib/*",
51
+ "./*"
52
+ ]
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "@gunshi/plugin": "0.26.3"
57
+ },
58
+ "peerDependencies": {
59
+ "@gunshi/plugin-global": "0.26.3"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@gunshi/plugin-global": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@intlify/core": "next",
68
+ "deno": "^2.3.3",
69
+ "jsr": "^0.13.4",
70
+ "jsr-exports-lint": "^0.4.1",
71
+ "messageformat": "4.0.0-12",
72
+ "publint": "^0.3.12",
73
+ "tsdown": "^0.12.3",
74
+ "typedoc": "^0.28.4",
75
+ "typedoc-plugin-markdown": "^4.6.3",
76
+ "@gunshi/shared": "0.26.3",
77
+ "@gunshi/resources": "0.26.3"
78
+ },
79
+ "scripts": {
80
+ "build": "tsdown",
81
+ "build:docs": "typedoc --excludeInternal",
82
+ "lint:jsr": "jsr publish --dry-run --allow-dirty",
83
+ "typecheck:deno": "deno check --import-map=../../importmap.json ./src"
84
+ }
85
+ }