@plop-next/i18n 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # @plop-next/i18n
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - "@plop-next/cli": minor
8
+
9
+ "@plop-next/core": minor
10
+
11
+ "@plop-next/i18n": minor
12
+
13
+ Refactor internal generator prompt handling from generator-select to generatorList across cli, core, and i18n.
14
+
15
+ Reserve generatorList for internal generator selection and improve validation and error messages.
16
+
17
+ Align theme selector flow and generated scaffold theme slots with generatorList.
18
+
19
+ Update i18n/theme integrations and tests to keep behavior consistent.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @plop-next/core@0.1.0
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # i18n
@@ -0,0 +1,341 @@
1
+ import { PlopNextCore, LocaleTag, LocaleTexts, HelpTexts } from '@plop-next/core';
2
+
3
+ type LocalesBundle = Record<string, LocaleTexts>;
4
+ type LocaleSource = LocaleTexts | LocalesBundle | string;
5
+ interface RegisterLocaleOptions {
6
+ /** If true, this locale becomes the active locale immediately. */
7
+ activate?: boolean;
8
+ }
9
+ /**
10
+ * PlopNextI18n
11
+ *
12
+ * Plugin class for `@plop-next/i18n`.
13
+ * Must be installed on a `PlopNextCore` instance before `useI18n` is called.
14
+ *
15
+ * @example
16
+ * import { PlopNextI18n } from "@plop-next/i18n";
17
+ * const i18nPlugin = new PlopNextI18n(plopNext);
18
+ * i18nPlugin.registerLocale("fr", frMessages);
19
+ * plopNext.useI18n({ force: "fr" });
20
+ */
21
+ declare class PlopNextI18n {
22
+ private readonly core;
23
+ private readonly registry;
24
+ private enabled;
25
+ private readonly translatableRules;
26
+ constructor(core: PlopNextCore);
27
+ install(): this;
28
+ /**
29
+ * Register (or extend) a locale's message map.
30
+ * Merges deeply with any previously registered messages for that locale.
31
+ *
32
+ * @param locale BCP-47 tag, e.g. "fr", "es", "ja"
33
+ * @param messages Flat or nested translation map
34
+ * @param options `activate: true` sets this locale as active immediately
35
+ */
36
+ registerLocale(locale: LocaleTag, texts: LocaleTexts | string, options?: RegisterLocaleOptions): this;
37
+ registerLocales(locales: LocaleSource, options?: RegisterLocaleOptions): this;
38
+ /**
39
+ * Add or override texts for a locale. Merges deeply with existing texts.
40
+ *
41
+ * @param locale Locale tag, e.g. "fr"
42
+ * @param texts Partial or full nested text map
43
+ */
44
+ registerTexts(locale: LocaleTag, texts: LocaleTexts | string): this;
45
+ registerTexts(localesOrTexts: LocaleSource): this;
46
+ /**
47
+ * Override a single text value identified by a dot-notation path.
48
+ *
49
+ * @param locale Locale tag, e.g. "fr"
50
+ * @param path Dot-notation key, e.g. "cli.selectGenerator"
51
+ * @param text The translated string or function
52
+ *
53
+ * @example
54
+ * i18n.registerText("fr", "cli.selectGenerator", "Choisissez un générateur");
55
+ */
56
+ registerText(locale: LocaleTag, path: string, text: unknown): this;
57
+ /**
58
+ * Return the currently active locale tag.
59
+ */
60
+ getActiveLocale(): LocaleTag;
61
+ /**
62
+ * Check if a locale has been registered.
63
+ */
64
+ hasLocale(locale: LocaleTag): boolean;
65
+ private resolveSingleLocaleTexts;
66
+ private resolveLocalesOrSingle;
67
+ private resolveFromPath;
68
+ private resolveScopedDirectory;
69
+ private parseScopedSourceFile;
70
+ private extractScopedObject;
71
+ private findLocaleExportKey;
72
+ private normalizeLocaleTag;
73
+ private resolveWelcomeMessageForLocale;
74
+ private emitI18nSourceWarning;
75
+ private parseSourceFile;
76
+ private parseJsonFile;
77
+ private parseModuleFile;
78
+ private normalizeObjectInput;
79
+ private isLocalesBundle;
80
+ private isLocaleLikeKey;
81
+ private looksLikeThemeObject;
82
+ private isPlainObject;
83
+ private unwrapModuleDefault;
84
+ private mergePlainObjects;
85
+ private normalize;
86
+ private enable;
87
+ private applyInquirerLocale;
88
+ private buildInquirerLocale;
89
+ private detectLocale;
90
+ private preparePrompts;
91
+ /**
92
+ * Resolve a prompt field translation with fallback chain:
93
+ * 1. Try with prompt name as-is (handles both plain names and indexed names like "name[0]")
94
+ * 2. If that doesn't resolve, try without any index suffix (for indexed arrays)
95
+ * 3. Fall back to provided default
96
+ *
97
+ * @example
98
+ * // For promptName="name[0]" and field="message":
99
+ * // Tries: "component.name[0].message" → "component.name.message" → fallback
100
+ */
101
+ private resolvePromptField;
102
+ private translateChoices;
103
+ private choiceKey;
104
+ /**
105
+ * Apply one translation rule to the (shallow-copied) prompt record.
106
+ *
107
+ * The implementation is purely functional: nested arrays / objects are
108
+ * reconstructed copy-on-write, so the original prompt config is never
109
+ * mutated.
110
+ */
111
+ private applyRule;
112
+ /**
113
+ * Recursively walk `segments` starting at `idx`, navigating into `current`.
114
+ * Returns a *new* value when a translation changed something, or `current`
115
+ * unchanged when nothing was modified (enabling cheap reference equality
116
+ * checks at every level).
117
+ */
118
+ private walkPath;
119
+ /**
120
+ * Translate `fieldName` on `obj`. Returns a *new* object when the translation
121
+ * differs from the original value, or `obj` itself when nothing changed.
122
+ */
123
+ private translateFieldValue;
124
+ }
125
+
126
+ declare class I18nRegistry {
127
+ private readonly locales;
128
+ private activeLocale;
129
+ constructor();
130
+ registerTexts(locale: LocaleTag, texts: LocaleTexts): void;
131
+ registerText(locale: LocaleTag, path: string, text: unknown): void;
132
+ registerLocale(locale: LocaleTag, texts: LocaleTexts): void;
133
+ setActiveLocale(locale: LocaleTag): void;
134
+ getActiveLocale(): LocaleTag;
135
+ hasLocale(locale: LocaleTag): boolean;
136
+ getLocaleValue(locale: LocaleTag, key: string): unknown;
137
+ t(key: string, args?: unknown[], defaultMessage?: string | ((...a: unknown[]) => string)): string;
138
+ private findValue;
139
+ /**
140
+ * Returns the `help` texts for the given locale, walking up parent locales
141
+ * (e.g. "fr-BE" → "fr" → "en") until a `help` section is found.
142
+ * Always returns at least the English default help texts.
143
+ */
144
+ getHelpTexts(locale: LocaleTag): HelpTexts;
145
+ /**
146
+ * Resolves help texts from the closest parent locale of `locale`.
147
+ * Returns `undefined` if no parent locale has help texts.
148
+ */
149
+ private resolveParentHelpTexts;
150
+ /**
151
+ * Returns an ordered list of candidate locales to try for `locale`.
152
+ * E.g. "fr-BE" → ["fr-BE", "fr", "en"]
153
+ */
154
+ private parentLocaleChain;
155
+ private deepMerge;
156
+ }
157
+
158
+ /**
159
+ * i18n-facing export of core-owned default English texts.
160
+ * Keep this alias for backward compatibility.
161
+ */
162
+ declare const EN_MESSAGES: LocaleTexts;
163
+ declare const BASE_LOCALE: LocaleTag;
164
+ /**
165
+ * Preferred explicit naming for consumers wanting the default text bundle.
166
+ */
167
+ declare const DEFAULT_TEXTS_EN: LocaleTexts;
168
+
169
+ /**
170
+ * Spanish translations for plop-next built-in messages.
171
+ * Covers the same key structure as `@plop-next/core` English messages.
172
+ */
173
+ declare const ES_MESSAGES: LocaleTexts;
174
+
175
+ /**
176
+ * French translations for plop-next built-in messages.
177
+ * Covers the same key structure as `@plop-next/core` English messages.
178
+ */
179
+ declare const FR_MESSAGES: LocaleTexts;
180
+
181
+ /**
182
+ * Portuguese translations for plop-next built-in messages.
183
+ * Covers the same key structure as `@plop-next/core` English messages.
184
+ */
185
+ declare const PT_MESSAGES: LocaleTexts;
186
+
187
+ /**
188
+ * Chinese translations for plop-next built-in messages.
189
+ * Covers the same key structure as `@plop-next/core` English messages.
190
+ */
191
+ declare const ZH_MESSAGES: LocaleTexts;
192
+
193
+ /**
194
+ * Texts for a single prompt field (indexed or not).
195
+ *
196
+ * @example
197
+ * {
198
+ * message: "What is your name?",
199
+ * hint: "(required)",
200
+ * description: "Your full name",
201
+ * validate: "Name is required",
202
+ * }
203
+ */
204
+ interface PromptFieldTexts {
205
+ message?: string;
206
+ hint?: string;
207
+ description?: string;
208
+ validate?: string;
209
+ instructions?: string;
210
+ helpText?: string;
211
+ noResults?: string;
212
+ searchingText?: string;
213
+ choices?: Record<string, string>;
214
+ [key: string]: unknown;
215
+ }
216
+ /**
217
+ * Texts for a generator with multiple prompts.
218
+ * Structure: promptName -> { message, hint, description, choices, ... }
219
+ * For arrays of prompts, use numeric indices: name[0], name[1], etc.
220
+ *
221
+ * @example
222
+ * {
223
+ * description: "Create a new component",
224
+ * mode: {
225
+ * message: "Quel mode ?",
226
+ * choices: { add: "Ajouter", remove: "Supprimer" }
227
+ * },
228
+ * name: {
229
+ * // For indexed prompts
230
+ * "[0]": { message: "First component name" },
231
+ * "[1]": { message: "Second component name" }
232
+ * }
233
+ * }
234
+ */
235
+ interface GeneratorTexts extends Record<string, unknown> {
236
+ description?: string;
237
+ [promptName: string]: PromptFieldTexts | string | unknown;
238
+ }
239
+ /**
240
+ * Texts for CLI interface (select generator, no generators, etc.)
241
+ *
242
+ * @example
243
+ * {
244
+ * welcome: "Welcome to plop-next!",
245
+ * selectGenerator: "Please choose a generator",
246
+ * noGenerators: "No generators registered",
247
+ * aborted: "Operation cancelled",
248
+ * done: "Done!",
249
+ * }
250
+ */
251
+ interface CliTexts {
252
+ welcome?: string;
253
+ welcomeMessage?: string | null;
254
+ selectGenerator?: string;
255
+ noGenerators?: string;
256
+ generatorNotFound?: string | ((...args: unknown[]) => string);
257
+ aborted?: string;
258
+ done?: string;
259
+ }
260
+ /**
261
+ * @inquirer/prompts locale configuration.
262
+ * These control UI elements like confirm (Yes/No), select help text, etc.
263
+ *
264
+ * @example
265
+ * {
266
+ * confirm: {
267
+ * yesLabel: "Yes",
268
+ * noLabel: "No",
269
+ * hintYes: "Y/n",
270
+ * hintNo: "y/N",
271
+ * },
272
+ * select: {
273
+ * helpNavigate: "navigate",
274
+ * helpSelect: "select",
275
+ * },
276
+ * }
277
+ */
278
+ interface InquirerTexts {
279
+ confirm?: {
280
+ yesLabel?: string;
281
+ noLabel?: string;
282
+ hintYes?: string;
283
+ hintNo?: string;
284
+ };
285
+ select?: {
286
+ helpNavigate?: string;
287
+ helpSelect?: string;
288
+ };
289
+ checkbox?: {
290
+ helpNavigate?: string;
291
+ helpSelect?: string;
292
+ helpSubmit?: string;
293
+ helpAll?: string;
294
+ helpInvert?: string;
295
+ };
296
+ search?: {
297
+ helpNavigate?: string;
298
+ helpSelect?: string;
299
+ };
300
+ expand?: Record<string, unknown>;
301
+ rawlist?: Record<string, unknown>;
302
+ editor?: {
303
+ waitingMessage?: string | ((enterKey: string) => string);
304
+ };
305
+ input?: Record<string, unknown>;
306
+ number?: Record<string, unknown>;
307
+ password?: {
308
+ maskedText?: string;
309
+ };
310
+ }
311
+ /**
312
+ * Complete i18n text bundle for a locale.
313
+ * Includes CLI texts, generator-specific texts, and @inquirer/prompts texts.
314
+ *
315
+ * @example
316
+ * {
317
+ * cli: { ... },
318
+ * inquirer: { ... },
319
+ * component: { description: "...", name: { ... } },
320
+ * myGenerator: { ... },
321
+ * }
322
+ */
323
+ interface LocalizedTextsBundle extends LocaleTexts {
324
+ cli?: CliTexts;
325
+ inquirer?: InquirerTexts;
326
+ [generatorName: string]: unknown;
327
+ }
328
+ /**
329
+ * Helper type to define all texts for a locale in a type-safe manner.
330
+ * Use as const to ensure proper typing:
331
+ *
332
+ * @example
333
+ * const frTexts: LocaleTextsDef = {
334
+ * cli: { selectGenerator: "Choisir un générateur" },
335
+ * inquirer: { confirm: { yesLabel: "Oui" } },
336
+ * component: { description: "Créer un composant" },
337
+ * } as const;
338
+ */
339
+ type LocaleTextsDef = LocalizedTextsBundle;
340
+
341
+ export { BASE_LOCALE, type CliTexts, DEFAULT_TEXTS_EN, EN_MESSAGES, ES_MESSAGES, FR_MESSAGES, type GeneratorTexts, I18nRegistry, type InquirerTexts, type LocaleTextsDef, type LocalizedTextsBundle, PT_MESSAGES, PlopNextI18n, type PromptFieldTexts, type RegisterLocaleOptions, ZH_MESSAGES };