@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 +24 -0
- package/README.md +1 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +1468 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1468 @@
|
|
|
1
|
+
// src/PlopNextI18n.ts
|
|
2
|
+
import {
|
|
3
|
+
createConfirmHandler,
|
|
4
|
+
createSelectHandler,
|
|
5
|
+
createCheckboxHandler,
|
|
6
|
+
createSearchHandler,
|
|
7
|
+
createEditorHandler,
|
|
8
|
+
createPasswordHandler
|
|
9
|
+
} from "@plop-next/core";
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
11
|
+
import { basename, extname, isAbsolute, resolve } from "path";
|
|
12
|
+
import { createRequire } from "module";
|
|
13
|
+
import { createLocalizedPrompts } from "@inquirer/i18n";
|
|
14
|
+
|
|
15
|
+
// src/locales/en.ts
|
|
16
|
+
import { CORE_DEFAULT_TEXTS } from "@plop-next/core";
|
|
17
|
+
var EN_MESSAGES = CORE_DEFAULT_TEXTS;
|
|
18
|
+
var BASE_LOCALE = "en";
|
|
19
|
+
var DEFAULT_TEXTS_EN = CORE_DEFAULT_TEXTS;
|
|
20
|
+
|
|
21
|
+
// src/locales/es.ts
|
|
22
|
+
var ES_MESSAGES = {
|
|
23
|
+
cli: {
|
|
24
|
+
title: "Bienvenido a plop-next! \u{1F680}",
|
|
25
|
+
welcomeMessage: null,
|
|
26
|
+
selectGenerator: "Seleccione un generador",
|
|
27
|
+
noGenerators: "No hay generadores registrados. Agreguelos en su plopfile.",
|
|
28
|
+
generatorNotFound: (name) => `Generador "${name}" no encontrado.`,
|
|
29
|
+
aborted: "Cancelado.",
|
|
30
|
+
done: "Listo!",
|
|
31
|
+
promptCancelled: "Consola cerrada por el usuario."
|
|
32
|
+
},
|
|
33
|
+
inquirer: {
|
|
34
|
+
confirm: {
|
|
35
|
+
yesLabel: "S\xED",
|
|
36
|
+
noLabel: "No",
|
|
37
|
+
hintYes: "S/n",
|
|
38
|
+
hintNo: "s/N"
|
|
39
|
+
},
|
|
40
|
+
select: {
|
|
41
|
+
helpNavigate: "navegar",
|
|
42
|
+
helpSelect: "seleccionar"
|
|
43
|
+
},
|
|
44
|
+
checkbox: {
|
|
45
|
+
helpNavigate: "navegar",
|
|
46
|
+
helpSelect: "seleccionar",
|
|
47
|
+
helpSubmit: "enviar",
|
|
48
|
+
helpAll: "todos",
|
|
49
|
+
helpInvert: "invertir"
|
|
50
|
+
},
|
|
51
|
+
search: {
|
|
52
|
+
helpNavigate: "navegar",
|
|
53
|
+
helpSelect: "seleccionar"
|
|
54
|
+
},
|
|
55
|
+
editor: {
|
|
56
|
+
waitingMessage: (enterKey) => `Presione ${enterKey} para lanzar su editor preferido.`
|
|
57
|
+
},
|
|
58
|
+
password: {
|
|
59
|
+
maskedText: "[entrada oculta]"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
actions: {
|
|
63
|
+
add: {
|
|
64
|
+
creating: (path) => `Creando ${path}`,
|
|
65
|
+
created: (path) => `\u2714 Creado ${path}`,
|
|
66
|
+
alreadyExists: (path) => `El archivo ya existe: ${path}`
|
|
67
|
+
},
|
|
68
|
+
modify: {
|
|
69
|
+
modifying: (path) => `Modificando ${path}`,
|
|
70
|
+
modified: (path) => `\u2714 Modificado ${path}`,
|
|
71
|
+
notFound: (path) => `Archivo no encontrado: ${path}`,
|
|
72
|
+
patternNotFound: (path) => `Patron no encontrado en: ${path}`
|
|
73
|
+
},
|
|
74
|
+
append: {
|
|
75
|
+
appending: (path) => `Anadiendo en ${path}`,
|
|
76
|
+
appended: (path) => `\u2714 Anadido en ${path}`
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
errors: {
|
|
80
|
+
unknownAction: (type) => `Tipo de accion desconocido: "${type}"`,
|
|
81
|
+
plopfileNotFound: "No se pudo encontrar un plopfile (plopfile.js o plopfile.ts).",
|
|
82
|
+
plopfileLoadFailed: (err) => `Error al cargar el plopfile: ${err}`,
|
|
83
|
+
generatorNotFound: (name) => `Generador "${name}" no encontrado.`,
|
|
84
|
+
noGenerators: "No hay generadores registrados. Agreguelos en su plopfile.",
|
|
85
|
+
invalidPrompt: (name, reason) => `Prompt invalido "${name}": ${reason}`,
|
|
86
|
+
bypassParse: (promptName, promptType, value, detail) => `No se puede asignar el valor de bypass "${value}" al prompt ${promptType} "${promptName}"${detail ? `: ${detail}` : ""}`,
|
|
87
|
+
plopfileLoad: (path) => `Error al cargar el plopfile: ${path}`,
|
|
88
|
+
plopfileExport: "El plopfile debe exportar una funcion por defecto.",
|
|
89
|
+
userCancelled: "Prompt cancelado por el usuario.",
|
|
90
|
+
plopfileNotFoundWarning: "No se encontro ningun plopfile. Cree un plopfile.js en su proyecto.",
|
|
91
|
+
forcedLangI18nMissing: (locale) => `La locale forzada "${locale}" se ignora porque @plop-next/i18n no esta instalado. Se usa ingles.`,
|
|
92
|
+
forcedLangUnavailable: (locale) => `La locale forzada "${locale}" no esta disponible. Se usa ingles.`
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* CLI `--help` display texts — Spanish.
|
|
96
|
+
* Read-only: cannot be overridden via `registerLocale` / `registerTexts`.
|
|
97
|
+
*/
|
|
98
|
+
help: {
|
|
99
|
+
usage: "Uso:",
|
|
100
|
+
usage1: "Elegir de la lista de generadores disponibles",
|
|
101
|
+
usage2: "Ejecutar un generador registrado bajo este nombre",
|
|
102
|
+
usage3: "Ejecutar el generador con datos de entrada para omitir prompts",
|
|
103
|
+
options: "Opciones:",
|
|
104
|
+
optHelp: "Mostrar esta ayuda",
|
|
105
|
+
optShowTypeNames: "Mostrar nombres de tipo en lugar de abreviaciones",
|
|
106
|
+
optInitTitle: "Inicializaci\xF3n del Plopfile",
|
|
107
|
+
optGenerateTitle: "Generar archivos de Locale, Texts, Theme",
|
|
108
|
+
optOthersTitle: "Otras opciones",
|
|
109
|
+
optInit: "Generar un plopfile.ts base",
|
|
110
|
+
optInitJs: "Generar un plopfile.js base",
|
|
111
|
+
optInitTs: "Generar un plopfile.ts base",
|
|
112
|
+
optDemo: "Generar un generador de ejemplo en el plopfile",
|
|
113
|
+
optI18n: "Inicializar el plopfile con soporte i18n",
|
|
114
|
+
optGenerate: "Generar un modelo: locale | textos | tema",
|
|
115
|
+
optPath: "Directorio de salida base para los archivos de modelo generados",
|
|
116
|
+
optExtension: "Extensi\xF3n de archivo generada: ts | js | json",
|
|
117
|
+
optIncludeCustomTexts: "Para la generaci\xF3n de locale, incluir las claves traducibles del plopfile",
|
|
118
|
+
optVersion: "Mostrar la version actual",
|
|
119
|
+
optForce: "Ejecutar el generador en modo forzado",
|
|
120
|
+
optLang: "Forzar la locale de visualizacion (ej. en, es, fr)",
|
|
121
|
+
danger: "el peligro espera a quienes se aventuran bajo esta linea",
|
|
122
|
+
lowPlopfile: "Ruta al plopfile",
|
|
123
|
+
lowCwd: "Directorio base para calcular rutas relativas durante la busqueda del plopfile",
|
|
124
|
+
lowPreload: "Cadena o arreglo de modulos para cargar antes de plop-next",
|
|
125
|
+
lowDest: "Escribir salida en esta carpeta en lugar de la carpeta padre del plopfile",
|
|
126
|
+
lowNoProgress: "Desactivar el spinner de progreso",
|
|
127
|
+
examples: "Ejemplos:"
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/locales/fr.ts
|
|
132
|
+
var FR_MESSAGES = {
|
|
133
|
+
cli: {
|
|
134
|
+
title: "Bienvenue dans plop-next ! \u{1F680}",
|
|
135
|
+
welcomeMessage: null,
|
|
136
|
+
selectGenerator: "Veuillez choisir un g\xE9n\xE9rateur",
|
|
137
|
+
noGenerators: "Aucun g\xE9n\xE9rateur enregistr\xE9. Ajoutez-en dans votre plopfile.",
|
|
138
|
+
generatorNotFound: (name) => `G\xE9n\xE9rateur "${name}" introuvable.`,
|
|
139
|
+
aborted: "Annul\xE9.",
|
|
140
|
+
done: "Termin\xE9 !",
|
|
141
|
+
promptCancelled: "Console ferm\xE9e par l'utilisateur."
|
|
142
|
+
},
|
|
143
|
+
inquirer: {
|
|
144
|
+
confirm: {
|
|
145
|
+
yesLabel: "Oui",
|
|
146
|
+
noLabel: "Non",
|
|
147
|
+
hintYes: "O/n",
|
|
148
|
+
hintNo: "o/N"
|
|
149
|
+
},
|
|
150
|
+
select: {
|
|
151
|
+
helpNavigate: "naviguer",
|
|
152
|
+
helpSelect: "s\xE9lectionner"
|
|
153
|
+
},
|
|
154
|
+
checkbox: {
|
|
155
|
+
helpNavigate: "naviguer",
|
|
156
|
+
helpSelect: "s\xE9lectionner",
|
|
157
|
+
helpSubmit: "soumettre",
|
|
158
|
+
helpAll: "tout",
|
|
159
|
+
helpInvert: "inverser"
|
|
160
|
+
},
|
|
161
|
+
search: {
|
|
162
|
+
helpNavigate: "naviguer",
|
|
163
|
+
helpSelect: "s\xE9lectionner"
|
|
164
|
+
},
|
|
165
|
+
editor: {
|
|
166
|
+
waitingMessage: (enterKey) => `Appuyez sur ${enterKey} pour lancer votre \xE9diteur pr\xE9f\xE9r\xE9.`
|
|
167
|
+
},
|
|
168
|
+
password: {
|
|
169
|
+
maskedText: "[saisie masqu\xE9e]"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
actions: {
|
|
173
|
+
add: {
|
|
174
|
+
creating: (path) => `Cr\xE9ation de ${path}`,
|
|
175
|
+
created: (path) => `\u2714 Cr\xE9\xE9 ${path}`,
|
|
176
|
+
alreadyExists: (path) => `Le fichier existe d\xE9j\xE0 : ${path}`
|
|
177
|
+
},
|
|
178
|
+
modify: {
|
|
179
|
+
modifying: (path) => `Modification de ${path}`,
|
|
180
|
+
modified: (path) => `\u2714 Modifi\xE9 ${path}`,
|
|
181
|
+
notFound: (path) => `Fichier introuvable : ${path}`,
|
|
182
|
+
patternNotFound: (path) => `Motif introuvable dans : ${path}`
|
|
183
|
+
},
|
|
184
|
+
append: {
|
|
185
|
+
appending: (path) => `Ajout dans ${path}`,
|
|
186
|
+
appended: (path) => `\u2714 Ajout\xE9 dans ${path}`
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
errors: {
|
|
190
|
+
unknownAction: (type) => `Type d'action inconnu : "${type}"`,
|
|
191
|
+
plopfileNotFound: "Impossible de trouver un plopfile (plopfile.js ou plopfile.ts).",
|
|
192
|
+
plopfileLoadFailed: (err) => `\xC9chec du chargement du plopfile : ${err}`,
|
|
193
|
+
generatorNotFound: (name) => `G\xE9n\xE9rateur "${name}" introuvable.`,
|
|
194
|
+
noGenerators: "Aucun g\xE9n\xE9rateur enregistr\xE9. Ajoutez-en dans votre plopfile.",
|
|
195
|
+
invalidPrompt: (name, reason) => `Prompt invalide "${name}" : ${reason}`,
|
|
196
|
+
bypassParse: (promptName, promptType, value, detail) => `Impossible d'assigner la valeur de bypass "${value}" au prompt ${promptType} "${promptName}"${detail ? `: ${detail}` : ""}`,
|
|
197
|
+
plopfileLoad: (path) => `\xC9chec du chargement du plopfile : ${path}`,
|
|
198
|
+
plopfileExport: "Le plopfile doit exporter une fonction par d\xE9faut.",
|
|
199
|
+
userCancelled: "Prompt annul\xE9 par l'utilisateur.",
|
|
200
|
+
plopfileNotFoundWarning: "Aucun plopfile trouv\xE9. Cr\xE9ez un plopfile.js dans votre projet.",
|
|
201
|
+
forcedLangI18nMissing: (locale) => `La locale forc\xE9e "${locale}" est ignor\xE9e car @plop-next/i18n n'est pas install\xE9. Repli sur l'anglais.`,
|
|
202
|
+
forcedLangUnavailable: (locale) => `La locale forc\xE9e "${locale}" n'est pas disponible. Repli sur l'anglais.`
|
|
203
|
+
},
|
|
204
|
+
/**
|
|
205
|
+
* CLI `--help` display texts — French.
|
|
206
|
+
* Read-only: cannot be overridden via `registerLocale` / `registerTexts`.
|
|
207
|
+
*/
|
|
208
|
+
help: {
|
|
209
|
+
usage: "Utilisation :",
|
|
210
|
+
usage1: "Choisir dans la liste des g\xE9n\xE9rateurs disponibles",
|
|
211
|
+
usage2: "Executer un g\xE9n\xE9rateur enregistre sous ce nom",
|
|
212
|
+
usage3: "Executer le g\xE9n\xE9rateur avec des donnees d'entree pour passer les prompts",
|
|
213
|
+
options: "Options :",
|
|
214
|
+
optHelp: "Afficher cette aide",
|
|
215
|
+
optShowTypeNames: "Afficher les noms de type au lieu des abr\xE9viations",
|
|
216
|
+
optInitTitle: "Initialisation du Plopfile",
|
|
217
|
+
optGenerateTitle: "G\xE9n\xE9rer des fichiers Locale, Texts, Theme",
|
|
218
|
+
optOthersTitle: "Autres options",
|
|
219
|
+
optInit: "G\xE9n\xE9rer un plopfile.ts de base",
|
|
220
|
+
optInitJs: "G\xE9n\xE9rer un plopfile.js de base",
|
|
221
|
+
optInitTs: "G\xE9n\xE9rer un plopfile.ts de base",
|
|
222
|
+
optDemo: "G\xE9n\xE9rer un g\xE9n\xE9rateur de demo dans le plopfile",
|
|
223
|
+
optI18n: "Initialiser le plopfile avec le support i18n",
|
|
224
|
+
optGenerate: "G\xE9n\xE9rer un mod\xE8le : locale | textes | th\xE8me",
|
|
225
|
+
optPath: "R\xE9pertoire de sortie de base pour les fichiers de mod\xE8le g\xE9n\xE9r\xE9s",
|
|
226
|
+
optExtension: "Extension de fichier g\xE9n\xE9r\xE9e : ts | js | json",
|
|
227
|
+
optIncludeCustomTexts: "Pour la g\xE9n\xE9ration de locale, inclure les cl\xE9s traduisibles du plopfile",
|
|
228
|
+
optVersion: "Afficher la version courante",
|
|
229
|
+
optForce: "Executer le g\xE9n\xE9rateur en mode force",
|
|
230
|
+
optLang: "Forcer la locale d'affichage (ex. en, fr)",
|
|
231
|
+
danger: "le danger attend ceux qui s'aventurent sous la ligne",
|
|
232
|
+
lowPlopfile: "Chemin vers le plopfile",
|
|
233
|
+
lowCwd: "Repertoire de base pour calculer les chemins relatifs pendant la recherche du plopfile",
|
|
234
|
+
lowPreload: "Chaine ou tableau de modules a charger avant plop-next",
|
|
235
|
+
lowDest: "\xC9crire la sortie dans ce dossier au lieu du dossier parent du plopfile",
|
|
236
|
+
lowNoProgress: "D\xE9sactiver le spinner de progression",
|
|
237
|
+
examples: "Exemples :"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/locales/pt.ts
|
|
242
|
+
var PT_MESSAGES = {
|
|
243
|
+
cli: {
|
|
244
|
+
title: "Bem-vindo ao plop-next! \u{1F680}",
|
|
245
|
+
welcomeMessage: null,
|
|
246
|
+
selectGenerator: "Selecione um gerador",
|
|
247
|
+
noGenerators: "Nenhum gerador registrado. Adicione-os no seu plopfile.",
|
|
248
|
+
generatorNotFound: (name) => `Gerador "${name}" nao encontrado.`,
|
|
249
|
+
aborted: "Cancelado.",
|
|
250
|
+
done: "Concluido!",
|
|
251
|
+
promptCancelled: "Console fechado pelo usuario."
|
|
252
|
+
},
|
|
253
|
+
inquirer: {
|
|
254
|
+
confirm: {
|
|
255
|
+
yesLabel: "Sim",
|
|
256
|
+
noLabel: "N\xE3o",
|
|
257
|
+
hintYes: "S/n",
|
|
258
|
+
hintNo: "s/N"
|
|
259
|
+
},
|
|
260
|
+
select: {
|
|
261
|
+
helpNavigate: "navegar",
|
|
262
|
+
helpSelect: "selecionar"
|
|
263
|
+
},
|
|
264
|
+
checkbox: {
|
|
265
|
+
helpNavigate: "navegar",
|
|
266
|
+
helpSelect: "selecionar",
|
|
267
|
+
helpSubmit: "enviar",
|
|
268
|
+
helpAll: "todos",
|
|
269
|
+
helpInvert: "inverter"
|
|
270
|
+
},
|
|
271
|
+
search: {
|
|
272
|
+
helpNavigate: "navegar",
|
|
273
|
+
helpSelect: "selecionar"
|
|
274
|
+
},
|
|
275
|
+
editor: {
|
|
276
|
+
waitingMessage: (enterKey) => `Pressione ${enterKey} para abrir seu editor preferido.`
|
|
277
|
+
},
|
|
278
|
+
password: {
|
|
279
|
+
maskedText: "[entrada mascarada]"
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
actions: {
|
|
283
|
+
add: {
|
|
284
|
+
creating: (path) => `Criando ${path}`,
|
|
285
|
+
created: (path) => `\u2714 Criado ${path}`,
|
|
286
|
+
alreadyExists: (path) => `O arquivo ja existe: ${path}`
|
|
287
|
+
},
|
|
288
|
+
modify: {
|
|
289
|
+
modifying: (path) => `Modificando ${path}`,
|
|
290
|
+
modified: (path) => `\u2714 Modificado ${path}`,
|
|
291
|
+
notFound: (path) => `Arquivo nao encontrado: ${path}`,
|
|
292
|
+
patternNotFound: (path) => `Padrao nao encontrado em: ${path}`
|
|
293
|
+
},
|
|
294
|
+
append: {
|
|
295
|
+
appending: (path) => `Anexando em ${path}`,
|
|
296
|
+
appended: (path) => `\u2714 Anexado em ${path}`
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
errors: {
|
|
300
|
+
unknownAction: (type) => `Tipo de acao desconhecido: "${type}"`,
|
|
301
|
+
plopfileNotFound: "Nao foi possivel encontrar um plopfile (plopfile.js ou plopfile.ts).",
|
|
302
|
+
plopfileLoadFailed: (err) => `Falha ao carregar o plopfile: ${err}`,
|
|
303
|
+
generatorNotFound: (name) => `Gerador "${name}" nao encontrado.`,
|
|
304
|
+
noGenerators: "Nenhum gerador registrado. Adicione-os no seu plopfile.",
|
|
305
|
+
invalidPrompt: (name, reason) => `Prompt invalido "${name}": ${reason}`,
|
|
306
|
+
bypassParse: (promptName, promptType, value, detail) => `Nao e possivel atribuir o valor de bypass "${value}" ao prompt ${promptType} "${promptName}"${detail ? `: ${detail}` : ""}`,
|
|
307
|
+
plopfileLoad: (path) => `Falha ao carregar o plopfile: ${path}`,
|
|
308
|
+
plopfileExport: "O plopfile deve exportar uma funcao padrao.",
|
|
309
|
+
userCancelled: "Prompt cancelado pelo usuario.",
|
|
310
|
+
plopfileNotFoundWarning: "Nenhum plopfile encontrado. Crie um plopfile.js no seu projeto.",
|
|
311
|
+
forcedLangI18nMissing: (locale) => `A locale forcada "${locale}" foi ignorada porque @plop-next/i18n nao esta instalado. Usando ingles.`,
|
|
312
|
+
forcedLangUnavailable: (locale) => `A locale forcada "${locale}" nao esta disponivel. Usando ingles.`
|
|
313
|
+
},
|
|
314
|
+
/**
|
|
315
|
+
* CLI `--help` display texts — Portuguese.
|
|
316
|
+
* Read-only: cannot be overridden via `registerLocale` / `registerTexts`.
|
|
317
|
+
*/
|
|
318
|
+
help: {
|
|
319
|
+
usage: "Uso:",
|
|
320
|
+
usage1: "Escolher na lista de geradores disponiveis",
|
|
321
|
+
usage2: "Executar um gerador registrado com este nome",
|
|
322
|
+
usage3: "Executar o gerador com dados de entrada para ignorar prompts",
|
|
323
|
+
options: "Opcoes:",
|
|
324
|
+
optHelp: "Mostrar esta ajuda",
|
|
325
|
+
optShowTypeNames: "Mostrar nomes de tipo em vez de abreviacoes",
|
|
326
|
+
optInitTitle: "Inicializa\xE7\xE3o do Plopfile",
|
|
327
|
+
optGenerateTitle: "Gerar arquivos de Locale, Texts, Theme",
|
|
328
|
+
optOthersTitle: "Outras op\xE7\xF5es",
|
|
329
|
+
optInit: "Gerar um plopfile.ts basico",
|
|
330
|
+
optInitJs: "Gerar um plopfile.js basico",
|
|
331
|
+
optInitTs: "Gerar um plopfile.ts basico",
|
|
332
|
+
optDemo: "Gerar um gerador de demo no plopfile",
|
|
333
|
+
optI18n: "Inicializar o plopfile com suporte i18n",
|
|
334
|
+
optGenerate: "Gerar um modelo: locale | textos | tema",
|
|
335
|
+
optPath: "Diret\xF3rio de sa\xEDda base para os arquivos de modelo gerados",
|
|
336
|
+
optExtension: "Extens\xE3o de arquivo gerada: ts | js | json",
|
|
337
|
+
optIncludeCustomTexts: "Para a gera\xE7\xE3o de locale, incluir as chaves traduz\xEDveis do plopfile",
|
|
338
|
+
optVersion: "Mostrar a versao atual",
|
|
339
|
+
optForce: "Executar o gerador em modo forcado",
|
|
340
|
+
optLang: "Forcar a locale de exibicao (ex. en, es, fr, pt)",
|
|
341
|
+
danger: "o perigo espera quem se aventura abaixo desta linha",
|
|
342
|
+
lowPlopfile: "Caminho para o plopfile",
|
|
343
|
+
lowCwd: "Diretorio base para calcular caminhos relativos durante a busca do plopfile",
|
|
344
|
+
lowPreload: "String ou array de modulos para carregar antes do plop-next",
|
|
345
|
+
lowDest: "Escrever a saida nesta pasta em vez da pasta pai do plopfile",
|
|
346
|
+
lowNoProgress: "Desativar o spinner de progresso",
|
|
347
|
+
examples: "Exemplos:"
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/locales/zh.ts
|
|
352
|
+
var ZH_MESSAGES = {
|
|
353
|
+
cli: {
|
|
354
|
+
title: "\u6B22\u8FCE\u4F7F\u7528 plop-next! \u{1F680}",
|
|
355
|
+
welcomeMessage: null,
|
|
356
|
+
selectGenerator: "\u8BF7\u9009\u62E9\u4E00\u4E2A\u751F\u6210\u5668",
|
|
357
|
+
noGenerators: "\u672A\u6CE8\u518C\u4EFB\u4F55\u751F\u6210\u5668\u3002\u8BF7\u5728\u60A8\u7684 plopfile \u4E2D\u6DFB\u52A0\u3002",
|
|
358
|
+
generatorNotFound: (name) => `\u672A\u627E\u5230\u751F\u6210\u5668 "${name}"\u3002`,
|
|
359
|
+
aborted: "\u5DF2\u53D6\u6D88\u3002",
|
|
360
|
+
done: "\u5B8C\u6210\uFF01",
|
|
361
|
+
promptCancelled: "\u7528\u6237\u5173\u95ED\u4E86\u63A7\u5236\u53F0\u3002"
|
|
362
|
+
},
|
|
363
|
+
inquirer: {
|
|
364
|
+
confirm: {
|
|
365
|
+
yesLabel: "\u662F",
|
|
366
|
+
noLabel: "\u5426",
|
|
367
|
+
hintYes: "\u662F/\u5426",
|
|
368
|
+
hintNo: "\u662F/\u5426"
|
|
369
|
+
},
|
|
370
|
+
select: {
|
|
371
|
+
helpNavigate: "\u5BFC\u822A",
|
|
372
|
+
helpSelect: "\u9009\u62E9"
|
|
373
|
+
},
|
|
374
|
+
checkbox: {
|
|
375
|
+
helpNavigate: "\u5BFC\u822A",
|
|
376
|
+
helpSelect: "\u9009\u62E9",
|
|
377
|
+
helpSubmit: "\u63D0\u4EA4",
|
|
378
|
+
helpAll: "\u5168\u9009",
|
|
379
|
+
helpInvert: "\u53CD\u9009"
|
|
380
|
+
},
|
|
381
|
+
search: {
|
|
382
|
+
helpNavigate: "\u5BFC\u822A",
|
|
383
|
+
helpSelect: "\u9009\u62E9"
|
|
384
|
+
},
|
|
385
|
+
editor: {
|
|
386
|
+
waitingMessage: (enterKey) => `\u6309 ${enterKey} \u952E\u542F\u52A8\u60A8\u7684\u9996\u9009\u7F16\u8F91\u5668\u3002`
|
|
387
|
+
},
|
|
388
|
+
password: {
|
|
389
|
+
maskedText: "[\u8F93\u5165\u5DF2\u9690\u85CF]"
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
actions: {
|
|
393
|
+
add: {
|
|
394
|
+
creating: (path) => `\u6B63\u5728\u521B\u5EFA ${path}`,
|
|
395
|
+
created: (path) => `\u2714 \u5DF2\u521B\u5EFA ${path}`,
|
|
396
|
+
alreadyExists: (path) => `\u6587\u4EF6\u5DF2\u5B58\u5728: ${path}`
|
|
397
|
+
},
|
|
398
|
+
modify: {
|
|
399
|
+
modifying: (path) => `\u6B63\u5728\u4FEE\u6539 ${path}`,
|
|
400
|
+
modified: (path) => `\u2714 \u5DF2\u4FEE\u6539 ${path}`,
|
|
401
|
+
notFound: (path) => `\u672A\u627E\u5230\u6587\u4EF6: ${path}`,
|
|
402
|
+
patternNotFound: (path) => `\u5728\u4EE5\u4E0B\u6587\u4EF6\u4E2D\u672A\u627E\u5230\u6A21\u5F0F: ${path}`
|
|
403
|
+
},
|
|
404
|
+
append: {
|
|
405
|
+
appending: (path) => `\u6B63\u5728\u8FFD\u52A0\u5230 ${path}`,
|
|
406
|
+
appended: (path) => `\u2714 \u5DF2\u8FFD\u52A0\u5230 ${path}`
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
errors: {
|
|
410
|
+
unknownAction: (type) => `\u672A\u77E5\u7684\u64CD\u4F5C\u7C7B\u578B: "${type}"`,
|
|
411
|
+
plopfileNotFound: "\u627E\u4E0D\u5230 plopfile (plopfile.js \u6216 plopfile.ts)\u3002",
|
|
412
|
+
plopfileLoadFailed: (err) => `\u52A0\u8F7D plopfile \u5931\u8D25: ${err}`,
|
|
413
|
+
generatorNotFound: (name) => `\u672A\u627E\u5230\u751F\u6210\u5668 "${name}"\u3002`,
|
|
414
|
+
noGenerators: "\u672A\u6CE8\u518C\u4EFB\u4F55\u751F\u6210\u5668\u3002\u8BF7\u5728\u60A8\u7684 plopfile \u4E2D\u6DFB\u52A0\u3002",
|
|
415
|
+
invalidPrompt: (name, reason) => `\u65E0\u6548\u7684 prompt "${name}": ${reason}`,
|
|
416
|
+
bypassParse: (promptName, promptType, value, detail) => `\u65E0\u6CD5\u5C06 bypass \u503C "${value}" \u8D4B\u7ED9 prompt ${promptType} "${promptName}"${detail ? `: ${detail}` : ""}`,
|
|
417
|
+
plopfileLoad: (path) => `\u52A0\u8F7D plopfile \u5931\u8D25: ${path}`,
|
|
418
|
+
plopfileExport: "plopfile \u5FC5\u987B\u5BFC\u51FA\u4E00\u4E2A\u9ED8\u8BA4\u51FD\u6570\u3002",
|
|
419
|
+
userCancelled: "\u7528\u6237\u53D6\u6D88\u4E86 prompt\u3002",
|
|
420
|
+
plopfileNotFoundWarning: "\u672A\u627E\u5230 plopfile\u3002\u8BF7\u5728\u9879\u76EE\u4E2D\u521B\u5EFA plopfile.js\u3002",
|
|
421
|
+
forcedLangI18nMissing: (locale) => `\u5F3A\u5236\u8BED\u8A00 "${locale}" \u5DF2\u88AB\u5FFD\u7565\uFF0C\u56E0\u4E3A\u672A\u5B89\u88C5 @plop-next/i18n\u3002\u5C06\u56DE\u9000\u5230\u82F1\u6587\u3002`,
|
|
422
|
+
forcedLangUnavailable: (locale) => `\u5F3A\u5236\u8BED\u8A00 "${locale}" \u4E0D\u53EF\u7528\u3002\u5C06\u56DE\u9000\u5230\u82F1\u6587\u3002`
|
|
423
|
+
},
|
|
424
|
+
/**
|
|
425
|
+
* CLI `--help` display texts — Chinese.
|
|
426
|
+
* Read-only: cannot be overridden via `registerLocale` / `registerTexts`.
|
|
427
|
+
*/
|
|
428
|
+
help: {
|
|
429
|
+
usage: "\u7528\u6CD5:",
|
|
430
|
+
usage1: "\u4ECE\u53EF\u7528\u751F\u6210\u5668\u5217\u8868\u4E2D\u9009\u62E9",
|
|
431
|
+
usage2: "\u6267\u884C\u4EE5\u8BE5\u540D\u79F0\u6CE8\u518C\u7684\u751F\u6210\u5668",
|
|
432
|
+
usage3: "\u6267\u884C\u751F\u6210\u5668\u5E76\u4F20\u5165\u8F93\u5165\u6570\u636E\u4EE5\u8DF3\u8FC7 prompts",
|
|
433
|
+
options: "\u9009\u9879:",
|
|
434
|
+
optHelp: "\u663E\u793A\u5E2E\u52A9",
|
|
435
|
+
optShowTypeNames: "\u663E\u793A\u5B8C\u6574\u7C7B\u578B\u540D\u79F0\u800C\u4E0D\u662F\u7F29\u5199",
|
|
436
|
+
optInitTitle: "Plopfile \u521D\u59CB\u5316",
|
|
437
|
+
optGenerateTitle: "\u751F\u6210 Locale\u3001Texts\u3001Theme \u6587\u4EF6",
|
|
438
|
+
optOthersTitle: "\u5176\u4ED6\u9009\u9879",
|
|
439
|
+
optInit: "\u751F\u6210\u57FA\u7840 plopfile.ts",
|
|
440
|
+
optInitJs: "\u751F\u6210\u57FA\u7840 plopfile.js",
|
|
441
|
+
optInitTs: "\u751F\u6210\u57FA\u7840 plopfile.ts",
|
|
442
|
+
optDemo: "\u5728 plopfile \u4E2D\u751F\u6210\u4E00\u4E2A demo \u751F\u6210\u5668",
|
|
443
|
+
optI18n: "\u4EE5 i18n \u652F\u6301\u521D\u59CB\u5316 plopfile",
|
|
444
|
+
optGenerate: "\u751F\u6210\u4E00\u4E2A\u6A21\u578B: locale | texts | theme",
|
|
445
|
+
optPath: "\u751F\u6210\u7684\u6A21\u578B\u6587\u4EF6\u7684\u57FA\u7840\u8F93\u51FA\u76EE\u5F55",
|
|
446
|
+
optExtension: "\u751F\u6210\u7684\u6587\u4EF6\u6269\u5C55\u540D: ts | js | json",
|
|
447
|
+
optIncludeCustomTexts: "\u5BF9\u4E8E locale \u751F\u6210\uFF0C\u5305\u62EC plopfile \u4E2D\u53EF\u7FFB\u8BD1\u7684\u952E",
|
|
448
|
+
optVersion: "\u663E\u793A\u5F53\u524D\u7248\u672C",
|
|
449
|
+
optForce: "\u4EE5\u5F3A\u5236\u6A21\u5F0F\u6267\u884C\u751F\u6210\u5668",
|
|
450
|
+
optLang: "\u5F3A\u5236\u663E\u793A\u8BED\u8A00 (\u4F8B\u5982 en, es, fr, pt, zh)",
|
|
451
|
+
danger: "\u8DE8\u8FC7\u6B64\u7EBF\uFF0C\u98CE\u9669\u81EA\u8D1F",
|
|
452
|
+
lowPlopfile: "plopfile \u8DEF\u5F84",
|
|
453
|
+
lowCwd: "\u5728\u67E5\u627E plopfile \u65F6\u7528\u4E8E\u8BA1\u7B97\u76F8\u5BF9\u8DEF\u5F84\u7684\u57FA\u7840\u76EE\u5F55",
|
|
454
|
+
lowPreload: "\u5728 plop-next \u4E4B\u524D\u52A0\u8F7D\u7684\u6A21\u5757\u5B57\u7B26\u4E32\u6216\u6570\u7EC4",
|
|
455
|
+
lowDest: "\u5C06\u8F93\u51FA\u5199\u5165\u6B64\u76EE\u5F55\uFF0C\u800C\u4E0D\u662F plopfile \u7684\u7236\u76EE\u5F55",
|
|
456
|
+
lowNoProgress: "\u7981\u7528\u8FDB\u5EA6 spinner",
|
|
457
|
+
examples: "\u793A\u4F8B:"
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/I18nRegistry.ts
|
|
462
|
+
function resolvePath(obj, path) {
|
|
463
|
+
return path.split(".").reduce(
|
|
464
|
+
(acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0,
|
|
465
|
+
obj
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
var I18nRegistry = class {
|
|
469
|
+
constructor() {
|
|
470
|
+
this.locales = /* @__PURE__ */ new Map();
|
|
471
|
+
this.activeLocale = BASE_LOCALE;
|
|
472
|
+
this.locales.set(BASE_LOCALE, EN_MESSAGES);
|
|
473
|
+
this.locales.set("es", ES_MESSAGES);
|
|
474
|
+
this.locales.set("fr", FR_MESSAGES);
|
|
475
|
+
this.locales.set("pt", PT_MESSAGES);
|
|
476
|
+
this.locales.set("zh", ZH_MESSAGES);
|
|
477
|
+
}
|
|
478
|
+
registerTexts(locale, texts) {
|
|
479
|
+
const existing = this.locales.get(locale) ?? {};
|
|
480
|
+
const { help: _ignored, ...safeTexts } = texts;
|
|
481
|
+
this.locales.set(locale, this.deepMerge(existing, safeTexts));
|
|
482
|
+
}
|
|
483
|
+
registerText(locale, path, text) {
|
|
484
|
+
if (path === "help" || path.startsWith("help.")) return;
|
|
485
|
+
const keys = path.split(".").filter(Boolean);
|
|
486
|
+
const nested = keys.reduceRight((acc, key) => ({ [key]: acc }), text);
|
|
487
|
+
this.registerTexts(locale, nested);
|
|
488
|
+
}
|
|
489
|
+
registerLocale(locale, texts) {
|
|
490
|
+
if (!this.locales.has(locale)) {
|
|
491
|
+
const parentHelp = this.resolveParentHelpTexts(locale);
|
|
492
|
+
const seed = parentHelp ? { help: parentHelp } : {};
|
|
493
|
+
this.locales.set(locale, seed);
|
|
494
|
+
}
|
|
495
|
+
this.registerTexts(locale, texts);
|
|
496
|
+
}
|
|
497
|
+
setActiveLocale(locale) {
|
|
498
|
+
this.activeLocale = locale;
|
|
499
|
+
}
|
|
500
|
+
getActiveLocale() {
|
|
501
|
+
return this.activeLocale;
|
|
502
|
+
}
|
|
503
|
+
hasLocale(locale) {
|
|
504
|
+
return this.locales.has(locale);
|
|
505
|
+
}
|
|
506
|
+
getLocaleValue(locale, key) {
|
|
507
|
+
const localeEntry = this.locales.get(locale);
|
|
508
|
+
if (!localeEntry) {
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
511
|
+
return resolvePath(localeEntry, key);
|
|
512
|
+
}
|
|
513
|
+
t(key, args = [], defaultMessage) {
|
|
514
|
+
const value = this.findValue(key);
|
|
515
|
+
if (value !== void 0) {
|
|
516
|
+
return typeof value === "function" ? String(value(...args)) : String(value);
|
|
517
|
+
}
|
|
518
|
+
if (defaultMessage !== void 0) {
|
|
519
|
+
return typeof defaultMessage === "function" ? defaultMessage(...args) : defaultMessage;
|
|
520
|
+
}
|
|
521
|
+
return key;
|
|
522
|
+
}
|
|
523
|
+
findValue(key) {
|
|
524
|
+
const active = this.locales.get(this.activeLocale);
|
|
525
|
+
const inActive = active ? resolvePath(active, key) : void 0;
|
|
526
|
+
if (inActive !== void 0) return inActive;
|
|
527
|
+
if (this.activeLocale !== BASE_LOCALE) {
|
|
528
|
+
const en = this.locales.get(BASE_LOCALE);
|
|
529
|
+
const inEn = en ? resolvePath(en, key) : void 0;
|
|
530
|
+
if (inEn !== void 0) return inEn;
|
|
531
|
+
}
|
|
532
|
+
return void 0;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Returns the `help` texts for the given locale, walking up parent locales
|
|
536
|
+
* (e.g. "fr-BE" → "fr" → "en") until a `help` section is found.
|
|
537
|
+
* Always returns at least the English default help texts.
|
|
538
|
+
*/
|
|
539
|
+
getHelpTexts(locale) {
|
|
540
|
+
for (const candidate of this.parentLocaleChain(locale)) {
|
|
541
|
+
const entry = this.locales.get(candidate);
|
|
542
|
+
if (entry && typeof entry["help"] === "object" && entry["help"] !== null) {
|
|
543
|
+
return entry["help"];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const en = this.locales.get(BASE_LOCALE);
|
|
547
|
+
return en?.["help"] ?? {};
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Resolves help texts from the closest parent locale of `locale`.
|
|
551
|
+
* Returns `undefined` if no parent locale has help texts.
|
|
552
|
+
*/
|
|
553
|
+
resolveParentHelpTexts(locale) {
|
|
554
|
+
const chain = this.parentLocaleChain(locale);
|
|
555
|
+
for (const candidate of chain.slice(1)) {
|
|
556
|
+
const entry = this.locales.get(candidate);
|
|
557
|
+
if (entry && typeof entry["help"] === "object" && entry["help"] !== null) {
|
|
558
|
+
return entry["help"];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Returns an ordered list of candidate locales to try for `locale`.
|
|
565
|
+
* E.g. "fr-BE" → ["fr-BE", "fr", "en"]
|
|
566
|
+
*/
|
|
567
|
+
parentLocaleChain(locale) {
|
|
568
|
+
const chain = [locale];
|
|
569
|
+
const parts = locale.split(/[-_]/);
|
|
570
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
571
|
+
chain.push(parts.slice(0, i).join("-"));
|
|
572
|
+
}
|
|
573
|
+
if (!chain.includes(BASE_LOCALE)) chain.push(BASE_LOCALE);
|
|
574
|
+
return chain;
|
|
575
|
+
}
|
|
576
|
+
deepMerge(target, source) {
|
|
577
|
+
const result = { ...target };
|
|
578
|
+
for (const [key, value] of Object.entries(source)) {
|
|
579
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof target[key] === "object" && target[key] !== null) {
|
|
580
|
+
result[key] = this.deepMerge(
|
|
581
|
+
target[key],
|
|
582
|
+
value
|
|
583
|
+
);
|
|
584
|
+
} else {
|
|
585
|
+
result[key] = value;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// src/PlopNextI18n.ts
|
|
593
|
+
var requireModule = createRequire(import.meta.url);
|
|
594
|
+
var SUPPORTED_I18N_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
595
|
+
".json",
|
|
596
|
+
".js",
|
|
597
|
+
".cjs",
|
|
598
|
+
".ts",
|
|
599
|
+
".cts"
|
|
600
|
+
]);
|
|
601
|
+
var SCOPED_LOCALE_FILE_PATTERN = /^([a-z]{2}(?:[-_][A-Za-z0-9]{2,8})*)\.locale\.(ts|js|json)$/i;
|
|
602
|
+
var SCOPED_TEXTS_FILE_PATTERN = /^([a-z]{2}(?:[-_][A-Za-z0-9]{2,8})*)\.texts\.(ts|js|json)$/i;
|
|
603
|
+
var RuntimeI18nSourceWarning = class _RuntimeI18nSourceWarning extends Error {
|
|
604
|
+
constructor(message) {
|
|
605
|
+
super(message);
|
|
606
|
+
this.code = "I18N_SOURCE_WARNING";
|
|
607
|
+
this.isOperational = true;
|
|
608
|
+
this.config = {
|
|
609
|
+
isWarning: true,
|
|
610
|
+
shouldExit: false,
|
|
611
|
+
exitCode: 0,
|
|
612
|
+
showStackTrace: false,
|
|
613
|
+
allowLogFile: false
|
|
614
|
+
};
|
|
615
|
+
this.name = "RuntimeI18nSourceWarning";
|
|
616
|
+
Object.setPrototypeOf(this, _RuntimeI18nSourceWarning.prototype);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
var PlopNextI18n = class {
|
|
620
|
+
constructor(core) {
|
|
621
|
+
this.core = core;
|
|
622
|
+
this.registry = new I18nRegistry();
|
|
623
|
+
this.enabled = false;
|
|
624
|
+
this.translatableRules = /* @__PURE__ */ new Map();
|
|
625
|
+
this.install();
|
|
626
|
+
}
|
|
627
|
+
install() {
|
|
628
|
+
const adapter = {
|
|
629
|
+
t: (key, args, fallback) => this.registry.t(key, args, fallback),
|
|
630
|
+
preparePrompts: (generatorName, prompts) => this.preparePrompts(generatorName, prompts),
|
|
631
|
+
getWelcomeMessage: () => {
|
|
632
|
+
const activeLocale = this.registry.getActiveLocale();
|
|
633
|
+
const activeValue = this.resolveWelcomeMessageForLocale(activeLocale);
|
|
634
|
+
if (activeValue !== null) {
|
|
635
|
+
return activeValue;
|
|
636
|
+
}
|
|
637
|
+
if (activeLocale === "en") {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
return this.resolveWelcomeMessageForLocale("en");
|
|
641
|
+
},
|
|
642
|
+
use: (options) => this.enable(options),
|
|
643
|
+
isEnabled: () => this.enabled,
|
|
644
|
+
hasLocale: (locale) => this.registry.hasLocale(locale),
|
|
645
|
+
registerLocale: (locale, messages, options) => {
|
|
646
|
+
this.registerLocale(locale, messages, options);
|
|
647
|
+
},
|
|
648
|
+
registerLocales: (locales, options) => {
|
|
649
|
+
this.registerLocales(locales, options);
|
|
650
|
+
},
|
|
651
|
+
registerTexts: (localeOrTexts, maybeTexts) => {
|
|
652
|
+
if (typeof maybeTexts === "undefined") {
|
|
653
|
+
this.registerTexts(localeOrTexts);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
this.registerTexts(localeOrTexts, maybeTexts);
|
|
657
|
+
},
|
|
658
|
+
registerText: (locale, path, text) => {
|
|
659
|
+
this.registerText(locale, path, text);
|
|
660
|
+
},
|
|
661
|
+
setLocale: (locale) => {
|
|
662
|
+
this.registry.setActiveLocale(locale);
|
|
663
|
+
},
|
|
664
|
+
getLocale: () => this.registry.getActiveLocale(),
|
|
665
|
+
registerTranslatableFields: (promptType, rules) => {
|
|
666
|
+
const existing = this.translatableRules.get(promptType) ?? [];
|
|
667
|
+
this.translatableRules.set(promptType, [...existing, ...rules]);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
this.core.setI18nAdapter(adapter);
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Register (or extend) a locale's message map.
|
|
675
|
+
* Merges deeply with any previously registered messages for that locale.
|
|
676
|
+
*
|
|
677
|
+
* @param locale BCP-47 tag, e.g. "fr", "es", "ja"
|
|
678
|
+
* @param messages Flat or nested translation map
|
|
679
|
+
* @param options `activate: true` sets this locale as active immediately
|
|
680
|
+
*/
|
|
681
|
+
registerLocale(locale, texts, options = {}) {
|
|
682
|
+
this.registry.registerLocale(
|
|
683
|
+
locale,
|
|
684
|
+
this.resolveSingleLocaleTexts(locale, texts)
|
|
685
|
+
);
|
|
686
|
+
if (options.activate) {
|
|
687
|
+
this.registry.setActiveLocale(locale);
|
|
688
|
+
}
|
|
689
|
+
return this;
|
|
690
|
+
}
|
|
691
|
+
registerLocales(locales, options = {}) {
|
|
692
|
+
const resolved = this.resolveLocalesOrSingle(
|
|
693
|
+
locales,
|
|
694
|
+
this.registry.getActiveLocale(),
|
|
695
|
+
"locales"
|
|
696
|
+
);
|
|
697
|
+
if (this.isLocalesBundle(resolved)) {
|
|
698
|
+
const entries = Object.entries(resolved);
|
|
699
|
+
for (const [locale, texts] of entries) {
|
|
700
|
+
this.registry.registerLocale(locale, texts);
|
|
701
|
+
}
|
|
702
|
+
if (options.activate && entries[0]) {
|
|
703
|
+
this.registry.setActiveLocale(entries[0][0]);
|
|
704
|
+
}
|
|
705
|
+
return this;
|
|
706
|
+
}
|
|
707
|
+
const active = this.registry.getActiveLocale();
|
|
708
|
+
this.registry.registerLocale(active, resolved);
|
|
709
|
+
if (options.activate) {
|
|
710
|
+
this.registry.setActiveLocale(active);
|
|
711
|
+
}
|
|
712
|
+
return this;
|
|
713
|
+
}
|
|
714
|
+
registerTexts(localeOrTexts, maybeTexts) {
|
|
715
|
+
if (typeof localeOrTexts === "string" && typeof maybeTexts !== "undefined") {
|
|
716
|
+
this.registry.registerTexts(
|
|
717
|
+
localeOrTexts,
|
|
718
|
+
this.resolveSingleLocaleTexts(localeOrTexts, maybeTexts)
|
|
719
|
+
);
|
|
720
|
+
return this;
|
|
721
|
+
}
|
|
722
|
+
const resolved = this.resolveLocalesOrSingle(
|
|
723
|
+
localeOrTexts,
|
|
724
|
+
this.registry.getActiveLocale(),
|
|
725
|
+
"texts"
|
|
726
|
+
);
|
|
727
|
+
if (this.isLocalesBundle(resolved)) {
|
|
728
|
+
for (const [locale, texts] of Object.entries(resolved)) {
|
|
729
|
+
this.registry.registerTexts(locale, texts);
|
|
730
|
+
}
|
|
731
|
+
return this;
|
|
732
|
+
}
|
|
733
|
+
this.registry.registerTexts(this.registry.getActiveLocale(), resolved);
|
|
734
|
+
return this;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Override a single text value identified by a dot-notation path.
|
|
738
|
+
*
|
|
739
|
+
* @param locale Locale tag, e.g. "fr"
|
|
740
|
+
* @param path Dot-notation key, e.g. "cli.selectGenerator"
|
|
741
|
+
* @param text The translated string or function
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* i18n.registerText("fr", "cli.selectGenerator", "Choisissez un générateur");
|
|
745
|
+
*/
|
|
746
|
+
registerText(locale, path, text) {
|
|
747
|
+
this.registry.registerText(locale, path, text);
|
|
748
|
+
return this;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Return the currently active locale tag.
|
|
752
|
+
*/
|
|
753
|
+
getActiveLocale() {
|
|
754
|
+
return this.registry.getActiveLocale();
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Check if a locale has been registered.
|
|
758
|
+
*/
|
|
759
|
+
hasLocale(locale) {
|
|
760
|
+
return this.registry.hasLocale(locale);
|
|
761
|
+
}
|
|
762
|
+
resolveSingleLocaleTexts(locale, input) {
|
|
763
|
+
const resolved = this.resolveLocalesOrSingle(input, locale, "locale");
|
|
764
|
+
if (this.isLocalesBundle(resolved)) {
|
|
765
|
+
if (resolved[locale]) {
|
|
766
|
+
return resolved[locale];
|
|
767
|
+
}
|
|
768
|
+
const entries = Object.entries(resolved);
|
|
769
|
+
if (entries.length === 1 && entries[0]) {
|
|
770
|
+
return entries[0][1];
|
|
771
|
+
}
|
|
772
|
+
throw new Error(
|
|
773
|
+
`Unable to resolve texts for locale "${locale}" from a multi-locale source.`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
return resolved;
|
|
777
|
+
}
|
|
778
|
+
resolveLocalesOrSingle(input, fallbackLocale, intent) {
|
|
779
|
+
if (typeof input === "string") {
|
|
780
|
+
return this.resolveFromPath(input, fallbackLocale, intent);
|
|
781
|
+
}
|
|
782
|
+
if (!this.isPlainObject(input)) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
"i18n source must be an object, a locale file path (.json/.js/.cjs/.ts/.cts), or a directory path."
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
return this.normalizeObjectInput(input, fallbackLocale, intent);
|
|
788
|
+
}
|
|
789
|
+
resolveFromPath(inputPath, fallbackLocale, intent) {
|
|
790
|
+
const absolutePath = isAbsolute(inputPath) ? inputPath : resolve(process.cwd(), inputPath);
|
|
791
|
+
if (!existsSync(absolutePath)) {
|
|
792
|
+
throw new Error(`i18n path not found: ${absolutePath}`);
|
|
793
|
+
}
|
|
794
|
+
const stats = statSync(absolutePath);
|
|
795
|
+
if (stats.isDirectory()) {
|
|
796
|
+
if (intent === "locales" || intent === "texts") {
|
|
797
|
+
return this.resolveScopedDirectory(absolutePath, fallbackLocale, intent);
|
|
798
|
+
}
|
|
799
|
+
const files = readdirSync(absolutePath).filter(
|
|
800
|
+
(name) => SUPPORTED_I18N_FILE_EXTENSIONS.has(extname(name).toLowerCase())
|
|
801
|
+
).sort();
|
|
802
|
+
if (files.length === 0) {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`No locale/text files found in directory: ${absolutePath}. Supported extensions: .json, .js, .cjs, .ts, .cts.`
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
const bundle = {};
|
|
808
|
+
for (const fileName of files) {
|
|
809
|
+
const filePath = resolve(absolutePath, fileName);
|
|
810
|
+
const parsed2 = this.parseSourceFile(filePath);
|
|
811
|
+
const normalized = this.normalizeObjectInput(
|
|
812
|
+
parsed2,
|
|
813
|
+
fallbackLocale,
|
|
814
|
+
intent
|
|
815
|
+
);
|
|
816
|
+
if (this.isLocalesBundle(normalized)) {
|
|
817
|
+
for (const [locale2, texts] of Object.entries(normalized)) {
|
|
818
|
+
bundle[locale2] = this.mergePlainObjects(
|
|
819
|
+
bundle[locale2] ?? {},
|
|
820
|
+
texts
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const locale = basename(fileName, extname(fileName));
|
|
826
|
+
if (!this.isLocaleLikeKey(locale) && intent !== "locale") {
|
|
827
|
+
throw new Error(
|
|
828
|
+
`Invalid ${intent} file name "${fileName}" in directory: ${absolutePath}. Expected a locale-like name such as "en.json", "fr.js", or "de.ts".`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
const targetLocale = this.isLocaleLikeKey(locale) ? locale : fallbackLocale;
|
|
832
|
+
bundle[targetLocale] = this.mergePlainObjects(
|
|
833
|
+
bundle[targetLocale] ?? {},
|
|
834
|
+
normalized
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return bundle;
|
|
838
|
+
}
|
|
839
|
+
const parsed = this.parseSourceFile(absolutePath);
|
|
840
|
+
return this.normalizeObjectInput(parsed, fallbackLocale, intent);
|
|
841
|
+
}
|
|
842
|
+
resolveScopedDirectory(absolutePath, fallbackLocale, intent) {
|
|
843
|
+
const files = readdirSync(absolutePath).sort();
|
|
844
|
+
const bundle = {};
|
|
845
|
+
let matchedFiles = 0;
|
|
846
|
+
for (const fileName of files) {
|
|
847
|
+
const match = intent === "locales" ? fileName.match(SCOPED_LOCALE_FILE_PATTERN) : fileName.match(SCOPED_TEXTS_FILE_PATTERN);
|
|
848
|
+
if (!match) {
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
matchedFiles += 1;
|
|
852
|
+
const locale = match[1];
|
|
853
|
+
if (!locale || !this.isLocaleLikeKey(locale)) {
|
|
854
|
+
this.emitI18nSourceWarning(
|
|
855
|
+
`Invalid ${intent} file name "${fileName}" in directory: ${absolutePath}. Expected format "<locale>.${intent === "locales" ? "locale" : "texts"}.<ts|js|json>".`
|
|
856
|
+
);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const filePath = resolve(absolutePath, fileName);
|
|
860
|
+
const scopedSource = this.parseScopedSourceFile(filePath, intent, locale);
|
|
861
|
+
if (!scopedSource) {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
const normalized = this.normalizeObjectInput(
|
|
865
|
+
scopedSource,
|
|
866
|
+
fallbackLocale,
|
|
867
|
+
intent
|
|
868
|
+
);
|
|
869
|
+
if (this.isLocalesBundle(normalized)) {
|
|
870
|
+
for (const [localeKey, texts] of Object.entries(normalized)) {
|
|
871
|
+
bundle[localeKey] = this.mergePlainObjects(
|
|
872
|
+
bundle[localeKey] ?? {},
|
|
873
|
+
texts
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
bundle[locale] = this.mergePlainObjects(bundle[locale] ?? {}, normalized);
|
|
879
|
+
}
|
|
880
|
+
if (matchedFiles === 0) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
`No ${intent} files found in directory: ${absolutePath}. Expected "<locale>.${intent === "locales" ? "locale" : "texts"}.<ts|js|json>" files.`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
return bundle;
|
|
886
|
+
}
|
|
887
|
+
parseScopedSourceFile(filePath, intent, fileLocale) {
|
|
888
|
+
const extension = extname(filePath).toLowerCase();
|
|
889
|
+
if (extension === ".json") {
|
|
890
|
+
const parsed = this.parseJsonFile(filePath);
|
|
891
|
+
const extracted2 = this.extractScopedObject(
|
|
892
|
+
parsed,
|
|
893
|
+
intent,
|
|
894
|
+
filePath,
|
|
895
|
+
fileLocale
|
|
896
|
+
);
|
|
897
|
+
if (!extracted2) {
|
|
898
|
+
return void 0;
|
|
899
|
+
}
|
|
900
|
+
if (this.looksLikeThemeObject(extracted2)) {
|
|
901
|
+
this.emitI18nSourceWarning(
|
|
902
|
+
`Ignored ${intent} file at ${filePath}: looks like a theme object.`
|
|
903
|
+
);
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
return extracted2;
|
|
907
|
+
}
|
|
908
|
+
if (!SUPPORTED_I18N_FILE_EXTENSIONS.has(extension)) {
|
|
909
|
+
this.emitI18nSourceWarning(
|
|
910
|
+
`Ignored ${intent} file at ${filePath}: unsupported extension "${extension || "<none>"}".`
|
|
911
|
+
);
|
|
912
|
+
return void 0;
|
|
913
|
+
}
|
|
914
|
+
let loaded;
|
|
915
|
+
try {
|
|
916
|
+
loaded = requireModule(filePath);
|
|
917
|
+
} catch (error) {
|
|
918
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Unable to load i18n module at ${filePath}: ${message}. If this file is ESM-only, import it in your plopfile and pass the object directly.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
const extracted = this.extractScopedObject(
|
|
924
|
+
loaded,
|
|
925
|
+
intent,
|
|
926
|
+
filePath,
|
|
927
|
+
fileLocale
|
|
928
|
+
);
|
|
929
|
+
if (!extracted) {
|
|
930
|
+
return void 0;
|
|
931
|
+
}
|
|
932
|
+
if (this.looksLikeThemeObject(extracted)) {
|
|
933
|
+
this.emitI18nSourceWarning(
|
|
934
|
+
`Ignored ${intent} file at ${filePath}: looks like a theme object.`
|
|
935
|
+
);
|
|
936
|
+
return void 0;
|
|
937
|
+
}
|
|
938
|
+
return extracted;
|
|
939
|
+
}
|
|
940
|
+
extractScopedObject(source, intent, filePath, fileLocale) {
|
|
941
|
+
if (!this.isPlainObject(source)) {
|
|
942
|
+
this.emitI18nSourceWarning(
|
|
943
|
+
`Ignored ${intent} file at ${filePath}: source must be an object.`
|
|
944
|
+
);
|
|
945
|
+
return void 0;
|
|
946
|
+
}
|
|
947
|
+
const record = source;
|
|
948
|
+
const selectorKeys = intent === "locales" ? ["locale", "Locale", "local", "Local"] : ["texts", "Texts", "text", "Text"];
|
|
949
|
+
for (const key of selectorKeys) {
|
|
950
|
+
if (!Object.prototype.hasOwnProperty.call(record, key)) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const selected = record[key];
|
|
954
|
+
if (this.isPlainObject(selected)) {
|
|
955
|
+
return selected;
|
|
956
|
+
}
|
|
957
|
+
this.emitI18nSourceWarning(
|
|
958
|
+
`Ignored ${intent} file at ${filePath}: "${key}" must be an object.`
|
|
959
|
+
);
|
|
960
|
+
return void 0;
|
|
961
|
+
}
|
|
962
|
+
if (Object.prototype.hasOwnProperty.call(record, "default")) {
|
|
963
|
+
const selected = record.default;
|
|
964
|
+
if (this.isPlainObject(selected)) {
|
|
965
|
+
return selected;
|
|
966
|
+
}
|
|
967
|
+
this.emitI18nSourceWarning(
|
|
968
|
+
`Ignored ${intent} file at ${filePath}: default export must be an object.`
|
|
969
|
+
);
|
|
970
|
+
return void 0;
|
|
971
|
+
}
|
|
972
|
+
const matchedLocaleKey = this.findLocaleExportKey(record, fileLocale);
|
|
973
|
+
if (matchedLocaleKey) {
|
|
974
|
+
const selected = record[matchedLocaleKey];
|
|
975
|
+
if (this.isPlainObject(selected)) {
|
|
976
|
+
return selected;
|
|
977
|
+
}
|
|
978
|
+
this.emitI18nSourceWarning(
|
|
979
|
+
`Ignored ${intent} file at ${filePath}: "${matchedLocaleKey}" export must be an object.`
|
|
980
|
+
);
|
|
981
|
+
return void 0;
|
|
982
|
+
}
|
|
983
|
+
this.emitI18nSourceWarning(
|
|
984
|
+
`Ignored ${intent} file at ${filePath}: expected a named export "${intent === "locales" ? "locale" : "texts"}", a locale export matching "${fileLocale}" (e.g. "${fileLocale.toLowerCase()}" or "${fileLocale.toUpperCase()}"), or a default export object.`
|
|
985
|
+
);
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
findLocaleExportKey(record, locale) {
|
|
989
|
+
const expected = this.normalizeLocaleTag(locale);
|
|
990
|
+
for (const key of Object.keys(record)) {
|
|
991
|
+
if (this.normalizeLocaleTag(key) === expected) {
|
|
992
|
+
return key;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return void 0;
|
|
996
|
+
}
|
|
997
|
+
normalizeLocaleTag(value) {
|
|
998
|
+
return value.trim().replace(/_/g, "-").toLowerCase();
|
|
999
|
+
}
|
|
1000
|
+
resolveWelcomeMessageForLocale(locale) {
|
|
1001
|
+
const candidateKeys = ["cli.welcomeMessage", "welcomeMessage"];
|
|
1002
|
+
for (const key of candidateKeys) {
|
|
1003
|
+
const value = this.registry.getLocaleValue(locale, key);
|
|
1004
|
+
if (typeof value === "string") {
|
|
1005
|
+
return value;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
emitI18nSourceWarning(message) {
|
|
1011
|
+
const warning = new RuntimeI18nSourceWarning(message);
|
|
1012
|
+
const reporter = this.core.reportWarning;
|
|
1013
|
+
if (typeof reporter === "function") {
|
|
1014
|
+
reporter.call(this.core, warning);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
console.warn(warning.message);
|
|
1018
|
+
}
|
|
1019
|
+
parseSourceFile(filePath) {
|
|
1020
|
+
const extension = extname(filePath).toLowerCase();
|
|
1021
|
+
if (extension === ".json") {
|
|
1022
|
+
return this.parseJsonFile(filePath);
|
|
1023
|
+
}
|
|
1024
|
+
if (!SUPPORTED_I18N_FILE_EXTENSIONS.has(extension)) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
`Unsupported i18n file extension "${extension || "<none>"}" at ${filePath}. Supported extensions: .json, .js, .cjs, .ts, .cts.`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
return this.parseModuleFile(filePath);
|
|
1030
|
+
}
|
|
1031
|
+
parseJsonFile(filePath) {
|
|
1032
|
+
let parsed;
|
|
1033
|
+
try {
|
|
1034
|
+
parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
`Invalid JSON file at ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
if (!this.isPlainObject(parsed)) {
|
|
1041
|
+
throw new Error(`JSON file must contain an object at root: ${filePath}`);
|
|
1042
|
+
}
|
|
1043
|
+
return parsed;
|
|
1044
|
+
}
|
|
1045
|
+
parseModuleFile(filePath) {
|
|
1046
|
+
let loaded;
|
|
1047
|
+
try {
|
|
1048
|
+
loaded = requireModule(filePath);
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1051
|
+
throw new Error(
|
|
1052
|
+
`Unable to load i18n module at ${filePath}: ${message}. If this file is ESM-only, import it in your plopfile and pass the object directly.`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
const unwrapped = this.unwrapModuleDefault(loaded);
|
|
1056
|
+
const value = typeof unwrapped === "function" ? this.unwrapModuleDefault(unwrapped()) : unwrapped;
|
|
1057
|
+
if (!this.isPlainObject(value)) {
|
|
1058
|
+
throw new Error(`i18n module must export an object at root: ${filePath}`);
|
|
1059
|
+
}
|
|
1060
|
+
return value;
|
|
1061
|
+
}
|
|
1062
|
+
normalizeObjectInput(input, fallbackLocale, intent) {
|
|
1063
|
+
if (this.looksLikeThemeObject(input)) {
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
`Invalid ${intent} source: looks like a theme object. Use core.setTheme(pathOrTheme) for theme files.`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
if (this.isLocalesBundle(input)) {
|
|
1069
|
+
return input;
|
|
1070
|
+
}
|
|
1071
|
+
if (this.isPlainObject(input)) {
|
|
1072
|
+
return input;
|
|
1073
|
+
}
|
|
1074
|
+
return { [fallbackLocale]: input };
|
|
1075
|
+
}
|
|
1076
|
+
isLocalesBundle(value) {
|
|
1077
|
+
if (!this.isPlainObject(value)) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
const entries = Object.entries(value);
|
|
1081
|
+
if (entries.length === 0) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
return entries.every(
|
|
1085
|
+
([key, val]) => this.isLocaleLikeKey(key) && this.isPlainObject(val)
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
isLocaleLikeKey(key) {
|
|
1089
|
+
return /^[a-z]{2}(?:[-_][A-Za-z0-9]{2,8})*$/i.test(key);
|
|
1090
|
+
}
|
|
1091
|
+
looksLikeThemeObject(value) {
|
|
1092
|
+
if (!this.isPlainObject(value)) {
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
const keys = Object.keys(value);
|
|
1096
|
+
if (keys.length === 0) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
const directThemeKeys = /* @__PURE__ */ new Set([
|
|
1100
|
+
"icon",
|
|
1101
|
+
"prefix",
|
|
1102
|
+
"spinner",
|
|
1103
|
+
"style",
|
|
1104
|
+
"validationFailureMode",
|
|
1105
|
+
"indexMode",
|
|
1106
|
+
"i18n",
|
|
1107
|
+
"keybindings",
|
|
1108
|
+
"plopNext",
|
|
1109
|
+
"waitingMessage",
|
|
1110
|
+
"maskedText",
|
|
1111
|
+
"disabledError",
|
|
1112
|
+
"input",
|
|
1113
|
+
"select",
|
|
1114
|
+
"generatorList",
|
|
1115
|
+
"list",
|
|
1116
|
+
"checkbox",
|
|
1117
|
+
"confirm",
|
|
1118
|
+
"search",
|
|
1119
|
+
"password",
|
|
1120
|
+
"expand",
|
|
1121
|
+
"editor",
|
|
1122
|
+
"number",
|
|
1123
|
+
"rawlist"
|
|
1124
|
+
]);
|
|
1125
|
+
return keys.some((key) => directThemeKeys.has(key));
|
|
1126
|
+
}
|
|
1127
|
+
isPlainObject(value) {
|
|
1128
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1129
|
+
}
|
|
1130
|
+
unwrapModuleDefault(value) {
|
|
1131
|
+
if (!this.isPlainObject(value)) {
|
|
1132
|
+
return value;
|
|
1133
|
+
}
|
|
1134
|
+
if (typeof value.default !== "undefined") {
|
|
1135
|
+
return value.default;
|
|
1136
|
+
}
|
|
1137
|
+
return value;
|
|
1138
|
+
}
|
|
1139
|
+
mergePlainObjects(target, source) {
|
|
1140
|
+
const out = { ...target };
|
|
1141
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1142
|
+
if (this.isPlainObject(value) && this.isPlainObject(out[key])) {
|
|
1143
|
+
out[key] = this.mergePlainObjects(out[key], value);
|
|
1144
|
+
} else {
|
|
1145
|
+
out[key] = value;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
return out;
|
|
1149
|
+
}
|
|
1150
|
+
normalize(value) {
|
|
1151
|
+
const [withoutEncoding = ""] = value.split(".");
|
|
1152
|
+
const [lang = ""] = withoutEncoding.split(/[_-]/);
|
|
1153
|
+
return lang.toLowerCase();
|
|
1154
|
+
}
|
|
1155
|
+
enable(options = { auto: true }) {
|
|
1156
|
+
this.enabled = true;
|
|
1157
|
+
const locale = options.force ?? (options.auto !== false ? this.detectLocale() : void 0);
|
|
1158
|
+
if (locale) {
|
|
1159
|
+
this.registry.setActiveLocale(locale);
|
|
1160
|
+
}
|
|
1161
|
+
this.applyInquirerLocale();
|
|
1162
|
+
}
|
|
1163
|
+
applyInquirerLocale() {
|
|
1164
|
+
const locale = this.buildInquirerLocale();
|
|
1165
|
+
const localized = createLocalizedPrompts(locale);
|
|
1166
|
+
this.core.registerPrompt(
|
|
1167
|
+
createConfirmHandler(localized.confirm)
|
|
1168
|
+
);
|
|
1169
|
+
this.core.registerPrompt(
|
|
1170
|
+
createSelectHandler(localized.select)
|
|
1171
|
+
);
|
|
1172
|
+
this.core.registerPrompt(
|
|
1173
|
+
createCheckboxHandler(localized.checkbox)
|
|
1174
|
+
);
|
|
1175
|
+
this.core.registerPrompt(
|
|
1176
|
+
createSearchHandler(localized.search)
|
|
1177
|
+
);
|
|
1178
|
+
this.core.registerPrompt(
|
|
1179
|
+
createEditorHandler(localized.editor)
|
|
1180
|
+
);
|
|
1181
|
+
this.core.registerPrompt(
|
|
1182
|
+
createPasswordHandler(localized.password)
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
buildInquirerLocale() {
|
|
1186
|
+
const t = (key, fallback) => {
|
|
1187
|
+
const full = `inquirer.${key}`;
|
|
1188
|
+
const val = this.registry.t(full);
|
|
1189
|
+
return val === full ? fallback : val;
|
|
1190
|
+
};
|
|
1191
|
+
return {
|
|
1192
|
+
confirm: {
|
|
1193
|
+
yesLabel: t("confirm.yesLabel", "Yes"),
|
|
1194
|
+
noLabel: t("confirm.noLabel", "No"),
|
|
1195
|
+
hintYes: t("confirm.hintYes", "Y/n"),
|
|
1196
|
+
hintNo: t("confirm.hintNo", "y/N")
|
|
1197
|
+
},
|
|
1198
|
+
select: {
|
|
1199
|
+
helpNavigate: t("select.helpNavigate", "navigate"),
|
|
1200
|
+
helpSelect: t("select.helpSelect", "select")
|
|
1201
|
+
},
|
|
1202
|
+
checkbox: {
|
|
1203
|
+
helpNavigate: t("checkbox.helpNavigate", "navigate"),
|
|
1204
|
+
helpSelect: t("checkbox.helpSelect", "select"),
|
|
1205
|
+
helpSubmit: t("checkbox.helpSubmit", "submit"),
|
|
1206
|
+
helpAll: t("checkbox.helpAll", "all"),
|
|
1207
|
+
helpInvert: t("checkbox.helpInvert", "invert")
|
|
1208
|
+
},
|
|
1209
|
+
search: {
|
|
1210
|
+
helpNavigate: t("search.helpNavigate", "navigate"),
|
|
1211
|
+
helpSelect: t("search.helpSelect", "select")
|
|
1212
|
+
},
|
|
1213
|
+
editor: {
|
|
1214
|
+
waitingMessage: (enterKey) => this.registry.t(
|
|
1215
|
+
"inquirer.editor.waitingMessage",
|
|
1216
|
+
[enterKey],
|
|
1217
|
+
`Press ${enterKey} to launch your preferred editor.`
|
|
1218
|
+
)
|
|
1219
|
+
},
|
|
1220
|
+
password: {
|
|
1221
|
+
maskedText: t("password.maskedText", "[input is masked]")
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
detectLocale() {
|
|
1226
|
+
for (const seg of (process.env["LANGUAGE"] ?? "").split(":")) {
|
|
1227
|
+
const lang = this.normalize(seg);
|
|
1228
|
+
if (lang) return lang;
|
|
1229
|
+
}
|
|
1230
|
+
for (const key of ["LC_ALL", "LC_MESSAGES", "LANG"]) {
|
|
1231
|
+
const lang = this.normalize(process.env[key] ?? "");
|
|
1232
|
+
if (lang) return lang;
|
|
1233
|
+
}
|
|
1234
|
+
try {
|
|
1235
|
+
const lang = this.normalize(
|
|
1236
|
+
Intl.DateTimeFormat().resolvedOptions().locale
|
|
1237
|
+
);
|
|
1238
|
+
if (lang) return lang;
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
1241
|
+
return "en";
|
|
1242
|
+
}
|
|
1243
|
+
preparePrompts(generatorName, prompts) {
|
|
1244
|
+
if (!this.enabled) {
|
|
1245
|
+
return prompts;
|
|
1246
|
+
}
|
|
1247
|
+
return prompts.map((prompt) => {
|
|
1248
|
+
const resolvedPrompt = { ...prompt };
|
|
1249
|
+
const resolvedPromptRecord = resolvedPrompt;
|
|
1250
|
+
const messageFallback = typeof prompt.message === "string" ? prompt.message : void 0;
|
|
1251
|
+
const message = this.resolvePromptField(
|
|
1252
|
+
generatorName,
|
|
1253
|
+
prompt.name,
|
|
1254
|
+
"message",
|
|
1255
|
+
messageFallback
|
|
1256
|
+
);
|
|
1257
|
+
if (message !== void 0) {
|
|
1258
|
+
resolvedPrompt.message = message;
|
|
1259
|
+
}
|
|
1260
|
+
const promptRecord = prompt;
|
|
1261
|
+
const choices = promptRecord["choices"];
|
|
1262
|
+
if (Array.isArray(choices)) {
|
|
1263
|
+
resolvedPromptRecord["choices"] = this.translateChoices(
|
|
1264
|
+
generatorName,
|
|
1265
|
+
prompt.name,
|
|
1266
|
+
choices
|
|
1267
|
+
);
|
|
1268
|
+
} else if (typeof choices === "function") {
|
|
1269
|
+
resolvedPromptRecord["choices"] = async (answers) => {
|
|
1270
|
+
const rawChoices = await choices(answers);
|
|
1271
|
+
return this.translateChoices(generatorName, prompt.name, rawChoices);
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
const translatableFields = [
|
|
1275
|
+
"description",
|
|
1276
|
+
"hint",
|
|
1277
|
+
"instructions",
|
|
1278
|
+
"helpText",
|
|
1279
|
+
"noResults",
|
|
1280
|
+
"searchingText"
|
|
1281
|
+
];
|
|
1282
|
+
for (const field of translatableFields) {
|
|
1283
|
+
const rawValue = resolvedPromptRecord[field];
|
|
1284
|
+
const fallback = typeof rawValue === "string" ? rawValue : void 0;
|
|
1285
|
+
const translated = this.resolvePromptField(
|
|
1286
|
+
generatorName,
|
|
1287
|
+
prompt.name,
|
|
1288
|
+
field,
|
|
1289
|
+
fallback
|
|
1290
|
+
);
|
|
1291
|
+
if (translated !== void 0) {
|
|
1292
|
+
resolvedPromptRecord[field] = translated;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
const customRules = this.translatableRules.get(prompt.type) ?? [];
|
|
1296
|
+
for (const rule of customRules) {
|
|
1297
|
+
this.applyRule(
|
|
1298
|
+
resolvedPromptRecord,
|
|
1299
|
+
rule,
|
|
1300
|
+
`${generatorName}.${prompt.name}`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
return resolvedPrompt;
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Resolve a prompt field translation with fallback chain:
|
|
1308
|
+
* 1. Try with prompt name as-is (handles both plain names and indexed names like "name[0]")
|
|
1309
|
+
* 2. If that doesn't resolve, try without any index suffix (for indexed arrays)
|
|
1310
|
+
* 3. Fall back to provided default
|
|
1311
|
+
*
|
|
1312
|
+
* @example
|
|
1313
|
+
* // For promptName="name[0]" and field="message":
|
|
1314
|
+
* // Tries: "component.name[0].message" → "component.name.message" → fallback
|
|
1315
|
+
*/
|
|
1316
|
+
resolvePromptField(generatorName, promptName, field, fallback) {
|
|
1317
|
+
const key = `${generatorName}.${promptName}.${field}`;
|
|
1318
|
+
let resolved = this.registry.t(key, [], void 0);
|
|
1319
|
+
if (resolved === key && /\[\d+\]/.test(promptName)) {
|
|
1320
|
+
const nameWithoutIndex = promptName.replace(/\[\d+\]$/, "");
|
|
1321
|
+
const fallbackKey = `${generatorName}.${nameWithoutIndex}.${field}`;
|
|
1322
|
+
resolved = this.registry.t(fallbackKey, [], void 0);
|
|
1323
|
+
}
|
|
1324
|
+
if (resolved === key && typeof fallback !== "undefined") {
|
|
1325
|
+
return fallback;
|
|
1326
|
+
}
|
|
1327
|
+
if (resolved === key) {
|
|
1328
|
+
return void 0;
|
|
1329
|
+
}
|
|
1330
|
+
return resolved;
|
|
1331
|
+
}
|
|
1332
|
+
translateChoices(generatorName, promptName, choices) {
|
|
1333
|
+
return choices.map((choice) => {
|
|
1334
|
+
if (typeof choice === "string") {
|
|
1335
|
+
const key = `${generatorName}.${promptName}.choices.${choice}`;
|
|
1336
|
+
return this.registry.t(key, [], choice);
|
|
1337
|
+
}
|
|
1338
|
+
if (choice && typeof choice === "object") {
|
|
1339
|
+
const rawName = choice.name;
|
|
1340
|
+
if (typeof rawName !== "string") {
|
|
1341
|
+
return choice;
|
|
1342
|
+
}
|
|
1343
|
+
const valueKey = this.choiceKey(choice);
|
|
1344
|
+
const key = `${generatorName}.${promptName}.choices.${valueKey}`;
|
|
1345
|
+
return {
|
|
1346
|
+
...choice,
|
|
1347
|
+
name: this.registry.t(key, [], rawName)
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
return choice;
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
choiceKey(choice) {
|
|
1354
|
+
if (typeof choice.value === "string" || typeof choice.value === "number") {
|
|
1355
|
+
return String(choice.value);
|
|
1356
|
+
}
|
|
1357
|
+
return choice.name;
|
|
1358
|
+
}
|
|
1359
|
+
// ── Custom translatable-field engine ────────────────────────────────
|
|
1360
|
+
/**
|
|
1361
|
+
* Apply one translation rule to the (shallow-copied) prompt record.
|
|
1362
|
+
*
|
|
1363
|
+
* The implementation is purely functional: nested arrays / objects are
|
|
1364
|
+
* reconstructed copy-on-write, so the original prompt config is never
|
|
1365
|
+
* mutated.
|
|
1366
|
+
*/
|
|
1367
|
+
applyRule(promptRecord, rule, keyBase) {
|
|
1368
|
+
if (!rule.path) {
|
|
1369
|
+
const fieldValue = promptRecord[rule.translateField];
|
|
1370
|
+
if (typeof fieldValue === "string") {
|
|
1371
|
+
const key = `${keyBase}.${rule.translateField}`;
|
|
1372
|
+
const translated = this.registry.t(key, [], fieldValue);
|
|
1373
|
+
if (translated !== fieldValue) {
|
|
1374
|
+
promptRecord[rule.translateField] = translated;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
const hasWildcard = rule.path.includes("#");
|
|
1380
|
+
const segments = !hasWildcard && rule.idField ? [...rule.path.split("."), "#"] : rule.path.split(".");
|
|
1381
|
+
const firstSeg = segments[0];
|
|
1382
|
+
if (!firstSeg) return;
|
|
1383
|
+
const original = promptRecord[firstSeg];
|
|
1384
|
+
const result = this.walkPath(
|
|
1385
|
+
original,
|
|
1386
|
+
segments,
|
|
1387
|
+
1,
|
|
1388
|
+
`${keyBase}.${firstSeg}`,
|
|
1389
|
+
rule
|
|
1390
|
+
);
|
|
1391
|
+
if (result !== original) {
|
|
1392
|
+
promptRecord[firstSeg] = result;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Recursively walk `segments` starting at `idx`, navigating into `current`.
|
|
1397
|
+
* Returns a *new* value when a translation changed something, or `current`
|
|
1398
|
+
* unchanged when nothing was modified (enabling cheap reference equality
|
|
1399
|
+
* checks at every level).
|
|
1400
|
+
*/
|
|
1401
|
+
walkPath(current, segments, idx, keyPath, rule) {
|
|
1402
|
+
if (idx >= segments.length) {
|
|
1403
|
+
return this.translateFieldValue(current, keyPath, rule.translateField);
|
|
1404
|
+
}
|
|
1405
|
+
const segment = segments[idx];
|
|
1406
|
+
if (segment === "#") {
|
|
1407
|
+
if (!Array.isArray(current)) return current;
|
|
1408
|
+
const isLastHash = !segments.slice(idx + 1).includes("#");
|
|
1409
|
+
let changed = false;
|
|
1410
|
+
const newArr = current.map((item, index) => {
|
|
1411
|
+
let keySegment;
|
|
1412
|
+
if (isLastHash && rule.idField) {
|
|
1413
|
+
const idVal = item !== null && typeof item === "object" && !Array.isArray(item) ? item[rule.idField] : void 0;
|
|
1414
|
+
keySegment = idVal != null ? String(idVal) : String(index);
|
|
1415
|
+
} else {
|
|
1416
|
+
keySegment = String(index);
|
|
1417
|
+
}
|
|
1418
|
+
const newItem = this.walkPath(
|
|
1419
|
+
item,
|
|
1420
|
+
segments,
|
|
1421
|
+
idx + 1,
|
|
1422
|
+
`${keyPath}.${keySegment}`,
|
|
1423
|
+
rule
|
|
1424
|
+
);
|
|
1425
|
+
if (newItem !== item) changed = true;
|
|
1426
|
+
return newItem;
|
|
1427
|
+
});
|
|
1428
|
+
return changed ? newArr : current;
|
|
1429
|
+
}
|
|
1430
|
+
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
1431
|
+
return current;
|
|
1432
|
+
const record = current;
|
|
1433
|
+
const next = record[segment];
|
|
1434
|
+
const newNext = this.walkPath(
|
|
1435
|
+
next,
|
|
1436
|
+
segments,
|
|
1437
|
+
idx + 1,
|
|
1438
|
+
`${keyPath}.${segment}`,
|
|
1439
|
+
rule
|
|
1440
|
+
);
|
|
1441
|
+
if (newNext === next) return current;
|
|
1442
|
+
return { ...record, [segment]: newNext };
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Translate `fieldName` on `obj`. Returns a *new* object when the translation
|
|
1446
|
+
* differs from the original value, or `obj` itself when nothing changed.
|
|
1447
|
+
*/
|
|
1448
|
+
translateFieldValue(obj, keyPath, fieldName) {
|
|
1449
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return obj;
|
|
1450
|
+
const record = obj;
|
|
1451
|
+
const fieldValue = record[fieldName];
|
|
1452
|
+
if (typeof fieldValue !== "string") return obj;
|
|
1453
|
+
const translated = this.registry.t(keyPath, [], fieldValue);
|
|
1454
|
+
if (translated === fieldValue) return obj;
|
|
1455
|
+
return { ...record, [fieldName]: translated };
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
export {
|
|
1459
|
+
BASE_LOCALE,
|
|
1460
|
+
DEFAULT_TEXTS_EN,
|
|
1461
|
+
EN_MESSAGES,
|
|
1462
|
+
ES_MESSAGES,
|
|
1463
|
+
FR_MESSAGES,
|
|
1464
|
+
I18nRegistry,
|
|
1465
|
+
PT_MESSAGES,
|
|
1466
|
+
PlopNextI18n,
|
|
1467
|
+
ZH_MESSAGES
|
|
1468
|
+
};
|