@x18n/builtins 0.0.2 → 0.0.5

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.
@@ -0,0 +1,1124 @@
1
+ import { defaultTemplates } from "./extractors.mjs";
2
+ import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-BwJwCUka.mjs";
3
+ import { braces, colon, dollars, doubleBraces, fluent, icu, linked, nesting, percentBraces, placeholders, printf } from "./placeholders.mjs";
4
+ import { r as regexScopes } from "./regex-B9TBGgXC.mjs";
5
+ import { isMessageLeaf } from "@x18n/core";
6
+ //#region src/presets/shared.ts
7
+ /** One plugin holding a library's call specs, regexes, namespace rules, defaults and extraction templates. */
8
+ function library(options) {
9
+ return {
10
+ name: `x18n:${options.name}`,
11
+ async config(draft, ctx) {
12
+ if (options.namespace) draft.namespace = {
13
+ separator: options.namespace.separator ?? draft.namespace.separator,
14
+ default: draft.namespace.default ?? options.namespace.default,
15
+ fromPath: options.namespace.fromPath ?? draft.namespace.fromPath
16
+ };
17
+ if (options.resources) draft.resources = [...draft.resources, ...options.resources.filter((spec) => !draft.resources.some((r) => r.pattern === spec.pattern))];
18
+ if (options.keyStyle && draft.keyStyle === "auto" && !ctx.user.keyStyle) draft.keyStyle = options.keyStyle;
19
+ draft.usage = {
20
+ ...draft.usage,
21
+ calls: [...draft.usage.calls, ...options.calls ?? []],
22
+ regex: [...draft.usage.regex, ...options.regex ?? []],
23
+ derivedKeys: [...draft.usage.derivedKeys, ...options.derivedKeys ?? []]
24
+ };
25
+ await options.extra?.(draft, ctx);
26
+ },
27
+ localeFinders: options.finders,
28
+ normalizeKey: options.normalizeKey,
29
+ transformResource: options.transformResource,
30
+ translatePrompt: options.translatePrompt,
31
+ extractors: options.templates ? [{
32
+ id: options.name,
33
+ templates: options.templates
34
+ }] : void 0
35
+ };
36
+ }
37
+ function dependsOn(names, prefixes = []) {
38
+ return (ctx) => ctx.hasAnyDependency(names) || [...ctx.dependencies].some((dep) => prefixes.some((prefix) => dep.startsWith(prefix)));
39
+ }
40
+ /** Extensions of JS and TS sources. */
41
+ const scriptExtensions = [
42
+ "js",
43
+ "jsx",
44
+ "mjs",
45
+ "cjs",
46
+ "ts",
47
+ "tsx",
48
+ "mts",
49
+ "cts"
50
+ ];
51
+ //#endregion
52
+ //#region src/presets/angular.ts
53
+ const templates = [
54
+ "html",
55
+ "ts",
56
+ "js"
57
+ ];
58
+ function ngxTranslate() {
59
+ return {
60
+ name: "ngx-translate",
61
+ detect: dependsOn(["@ngx-translate/core"]),
62
+ plugins: [library({
63
+ name: "ngx-translate",
64
+ calls: [{ callee: [
65
+ "translate.instant",
66
+ "translate.get",
67
+ "translate.stream",
68
+ "translateService.instant",
69
+ "translateService.get",
70
+ "translateService.stream",
71
+ "_"
72
+ ] }],
73
+ regex: [{
74
+ extensions: templates,
75
+ patterns: [
76
+ "[`'\"]({key})[`'\"][\\s\\n]*\\|[\\s\\n]*translate\\b",
77
+ "[\\s\\n]translate>[\\s\\n]*({key})[\\s\\n]*</",
78
+ "\\[translate\\]=[\"']'({key})'[\"']",
79
+ "\\stranslate=[\"']({key})[\"']"
80
+ ]
81
+ }],
82
+ resources: [{ pattern: "src/assets/i18n/{locale}.json" }, { pattern: "src/assets/i18n/{locale}/{namespaces}.json" }],
83
+ templates: [{
84
+ template: "{{ '{key}' | translate }}",
85
+ kinds: ["html-text"]
86
+ }, {
87
+ template: "this.translate.instant('{key}'{args})",
88
+ kinds: ["js-string", "js-template"]
89
+ }]
90
+ }), placeholders(doubleBraces)]
91
+ };
92
+ }
93
+ function transloco() {
94
+ return {
95
+ name: "transloco",
96
+ detect: dependsOn(["@jsverse/transloco", "@ngneat/transloco"]),
97
+ plugins: [
98
+ library({
99
+ name: "transloco",
100
+ namespace: { fromPath: "camel" },
101
+ calls: [{ callee: [
102
+ "translate",
103
+ "translocoService.translate",
104
+ "transloco.translate",
105
+ "translocoService.selectTranslate",
106
+ "transloco.selectTranslate"
107
+ ] }],
108
+ regex: [{
109
+ extensions: templates,
110
+ patterns: [
111
+ "[`'\"]({key})[`'\"][\\s\\n]*\\|[\\s\\n]*transloco\\b",
112
+ "[^\\w\\d]t\\([\\s\\n]*['\"`]({key})['\"`]",
113
+ "[^*\\w\\d]transloco=['\"`]({key})['\"`]"
114
+ ]
115
+ }],
116
+ resources: [{ pattern: "src/assets/i18n/{locale}.json" }, { pattern: "src/assets/i18n/{namespaces}/{locale}.json" }],
117
+ templates: [{
118
+ template: "{{ '{key}' | transloco }}",
119
+ kinds: ["html-text"]
120
+ }, {
121
+ template: "translate('{key}'{args})",
122
+ kinds: ["js-string", "js-template"],
123
+ imports: ["import { translate } from '@jsverse/transloco'"]
124
+ }]
125
+ }),
126
+ regexScopes("\\*transloco=\"[^\"]*read:\\s*'([^']+)'", "transloco-read", {
127
+ as: "keyPrefix",
128
+ extensions: ["html"]
129
+ }),
130
+ placeholders(doubleBraces)
131
+ ]
132
+ };
133
+ }
134
+ //#endregion
135
+ //#region src/presets/nls.ts
136
+ const nlsFile = /^(?:(.*)\/)?package\.nls(?:\.([\w-]+))?\.json$/;
137
+ const bundleFile = /^(?:(.*)\/)?bundle\.l10n(?:\.([\w-]+))?\.json$/;
138
+ /** VS Code extension strings: `package.nls[.locale].json` and `l10n/bundle.l10n[.locale].json`. */
139
+ const nlsFinder = {
140
+ id: "nls",
141
+ async find(ctx) {
142
+ const paths = await ctx.host.glob(["**/package.nls*.json", "**/bundle.l10n*.json"], {
143
+ ignore: ["**/node_modules/**"],
144
+ gitignore: true
145
+ });
146
+ const out = [];
147
+ for (const path of paths) {
148
+ const nls = nlsFile.exec(path);
149
+ const bundle = nls ? void 0 : bundleFile.exec(path);
150
+ const match = nls ?? bundle;
151
+ if (!match) continue;
152
+ out.push({
153
+ path,
154
+ locale: match[2] ?? ctx.config.sourceLocale,
155
+ namespace: void 0,
156
+ format: "json"
157
+ });
158
+ }
159
+ return out;
160
+ }
161
+ };
162
+ //#endregion
163
+ //#region src/presets/js.ts
164
+ const vueI18nCalls = [{ callee: [
165
+ "$t",
166
+ "t",
167
+ "tc",
168
+ "$tc",
169
+ "te",
170
+ "$te",
171
+ "tm",
172
+ "$tm",
173
+ "i18n.t",
174
+ "i18n.global.t",
175
+ "$i18n.t",
176
+ "i18n.tc",
177
+ "i18n.te"
178
+ ] }, {
179
+ callee: "useI18n",
180
+ factory: true,
181
+ returns: [
182
+ "t",
183
+ "tc",
184
+ "te",
185
+ "tm"
186
+ ]
187
+ }];
188
+ function vueI18n() {
189
+ return {
190
+ name: "vue-i18n",
191
+ detect: dependsOn([
192
+ "vue-i18n",
193
+ "vuex-i18n",
194
+ "petite-vue-i18n",
195
+ "vue-i18n-bridge"
196
+ ], ["@intlify/"]),
197
+ plugins: [
198
+ library({
199
+ name: "vue-i18n",
200
+ calls: vueI18nCalls,
201
+ templates: [
202
+ {
203
+ template: "{{ $t('{key}'{args}) }}",
204
+ kinds: ["vue-template-text"]
205
+ },
206
+ {
207
+ template: ":{attr}=\"$t('{key}'{args})\"",
208
+ kinds: ["vue-attribute"]
209
+ },
210
+ {
211
+ template: "t('{key}'{args})",
212
+ kinds: ["js-string", "js-template"],
213
+ files: ["**/*.vue"],
214
+ imports: ["import { useI18n } from 'vue-i18n'", "const { t } = useI18n()"]
215
+ }
216
+ ]
217
+ }),
218
+ placeholders({
219
+ ...braces,
220
+ implicit: ["n", "count"]
221
+ }, linked),
222
+ sfcBlocks()
223
+ ]
224
+ };
225
+ }
226
+ function nuxtI18n() {
227
+ return {
228
+ name: "nuxt-i18n",
229
+ detect: dependsOn(["@nuxtjs/i18n", "nuxt-i18n"]),
230
+ plugins: [...vueI18n().plugins, {
231
+ name: "x18n:nuxt-i18n",
232
+ async config(draft, ctx) {
233
+ const nuxtConfig = await ctx.host.readFile("nuxt.config.ts") ?? await ctx.host.readFile("nuxt.config.js");
234
+ const langDir = nuxtConfig ? /langDir:\s*['"]([^'"]+)['"]/.exec(nuxtConfig)?.[1]?.replace(/^\.\/|\/$/g, "") : void 0;
235
+ const dirs = langDir ? [`i18n/${langDir}`, langDir] : [
236
+ "i18n/locales",
237
+ "locales",
238
+ "lang"
239
+ ];
240
+ draft.resources = [...draft.resources, ...dirs.map((dir) => ({ pattern: `${dir}/{locale}.{ext}` }))];
241
+ }
242
+ }]
243
+ };
244
+ }
245
+ /** `defaultNS` from the i18next init code (`i18n.ts`, `next-i18next.config.js`), as keys without a namespace use it. */
246
+ async function findDefaultNamespace(host) {
247
+ const files = await host.glob(["**/*i18n*.{js,mjs,cjs,ts,mts,cts}", "**/i18n/index.{js,ts}"], {
248
+ ignore: ["**/node_modules/**", "**/dist/**"],
249
+ gitignore: true
250
+ });
251
+ for (const file of files.slice(0, 20)) {
252
+ const found = /\bdefaultNS\s*:\s*['"`]([^'"`]+)['"`]/.exec(await host.readFile(file) ?? "");
253
+ if (found) return found[1];
254
+ }
255
+ }
256
+ const i18nextPlugin = library({
257
+ name: "i18next",
258
+ namespace: {
259
+ separator: ":",
260
+ default: "translation",
261
+ fromPath: "slash"
262
+ },
263
+ extra: async (draft, ctx) => {
264
+ const found = ctx.user.namespace?.default ? void 0 : await findDefaultNamespace(ctx.host);
265
+ if (found) draft.namespace = {
266
+ ...draft.namespace,
267
+ default: found
268
+ };
269
+ },
270
+ calls: [
271
+ {
272
+ callee: [
273
+ "t",
274
+ "i18next.t",
275
+ "i18n.t"
276
+ ],
277
+ namespace: {
278
+ arg: 1,
279
+ property: "ns"
280
+ }
281
+ },
282
+ {
283
+ callee: [
284
+ "getFixedT",
285
+ "i18next.getFixedT",
286
+ "i18n.getFixedT"
287
+ ],
288
+ factory: true,
289
+ returns: ["t"],
290
+ namespace: { arg: 1 },
291
+ keyPrefix: { arg: 2 }
292
+ },
293
+ {
294
+ callee: "tr",
295
+ types: ["TFunction"]
296
+ }
297
+ ],
298
+ derivedKeys: ["{key}_*"],
299
+ translatePrompt: (draft, ctx) => {
300
+ draft.notes.push(`i18next plural keys end in _${ctx.localeInfo.pluralCategories.join(", _")} for ${ctx.targetLocale}; write one key per category.`);
301
+ },
302
+ templates: [{
303
+ template: "i18next.t('{key}'{args})",
304
+ kinds: ["js-string", "js-template"],
305
+ imports: ["import i18next from 'i18next'"]
306
+ }]
307
+ });
308
+ function i18next() {
309
+ return {
310
+ name: "i18next",
311
+ detect: dependsOn(["i18next"]),
312
+ plugins: [i18nextPlugin, placeholders(doubleBraces, nesting)]
313
+ };
314
+ }
315
+ function reactI18next() {
316
+ return {
317
+ name: "react-i18next",
318
+ detect: dependsOn(["react-i18next", "next-i18next"]),
319
+ plugins: [
320
+ i18nextPlugin,
321
+ placeholders(doubleBraces, nesting),
322
+ library({
323
+ name: "react-i18next",
324
+ calls: [
325
+ {
326
+ callee: "useTranslation",
327
+ factory: true,
328
+ returns: ["t"],
329
+ namespace: { arg: 0 },
330
+ keyPrefix: {
331
+ arg: 1,
332
+ property: "keyPrefix"
333
+ }
334
+ },
335
+ {
336
+ callee: "Trans",
337
+ key: {
338
+ arg: 0,
339
+ property: "i18nKey"
340
+ },
341
+ namespace: {
342
+ arg: 0,
343
+ property: "ns"
344
+ }
345
+ },
346
+ {
347
+ callee: "Translation",
348
+ factory: true,
349
+ returns: ["t"],
350
+ namespace: {
351
+ arg: 0,
352
+ property: "ns"
353
+ }
354
+ }
355
+ ],
356
+ templates: [{
357
+ template: "<Trans i18nKey=\"{key}\" />",
358
+ kinds: ["jsx-text"],
359
+ imports: ["import { Trans } from 'react-i18next'"]
360
+ }, {
361
+ template: "{attr}={i18next.t('{key}'{args})}",
362
+ kinds: ["jsx-attribute"],
363
+ imports: ["import i18next from 'i18next'"]
364
+ }]
365
+ }),
366
+ regexScopes("\\bwithTranslation\\(\\s*\\[?\\s*['\"`]([^'\"`]+)['\"`]", "react-i18next-hoc", {
367
+ wholeFile: true,
368
+ extensions: scriptExtensions
369
+ })
370
+ ]
371
+ };
372
+ }
373
+ function nextIntl() {
374
+ return {
375
+ name: "next-intl",
376
+ detect: dependsOn(["next-intl", "use-intl"]),
377
+ plugins: [library({
378
+ name: "next-intl",
379
+ calls: [{
380
+ callee: ["useTranslations", "getTranslations"],
381
+ factory: true,
382
+ returns: [
383
+ "t",
384
+ "t.rich",
385
+ "t.markup",
386
+ "t.raw",
387
+ "t.has"
388
+ ],
389
+ keyPrefix: { arg: 0 }
390
+ }, {
391
+ callee: "getTranslations",
392
+ factory: true,
393
+ returns: [
394
+ "t",
395
+ "t.rich",
396
+ "t.markup",
397
+ "t.raw",
398
+ "t.has"
399
+ ],
400
+ keyPrefix: {
401
+ arg: 0,
402
+ property: "namespace"
403
+ }
404
+ }],
405
+ resources: [{ pattern: "messages/{locale}.json" }, { pattern: "src/messages/{locale}.json" }],
406
+ templates: [
407
+ {
408
+ template: "{t('{key}'{args})}",
409
+ kinds: ["jsx-text"]
410
+ },
411
+ {
412
+ template: "{attr}={t('{key}'{args})}",
413
+ kinds: ["jsx-attribute"]
414
+ },
415
+ {
416
+ template: "t('{key}'{args})",
417
+ kinds: ["js-string", "js-template"]
418
+ }
419
+ ]
420
+ }), placeholders(icu)]
421
+ };
422
+ }
423
+ function reactIntl() {
424
+ return {
425
+ name: "react-intl",
426
+ detect: dependsOn(["react-intl"], ["@formatjs/"]),
427
+ plugins: [library({
428
+ name: "react-intl",
429
+ calls: [{
430
+ callee: [
431
+ "intl.formatMessage",
432
+ "formatMessage",
433
+ "defineMessage",
434
+ "FormattedMessage"
435
+ ],
436
+ key: {
437
+ arg: 0,
438
+ property: "id"
439
+ }
440
+ }, {
441
+ callee: "defineMessages",
442
+ key: {
443
+ arg: 0,
444
+ property: "*.id"
445
+ }
446
+ }],
447
+ templates: [
448
+ {
449
+ template: "<FormattedMessage id=\"{key}\" />",
450
+ kinds: ["jsx-text"],
451
+ imports: ["import { FormattedMessage } from 'react-intl'"]
452
+ },
453
+ {
454
+ template: "{attr}={intl.formatMessage({ id: '{key}' }{args})}",
455
+ kinds: ["jsx-attribute"]
456
+ },
457
+ {
458
+ template: "intl.formatMessage({ id: '{key}' }{args})",
459
+ kinds: ["js-string", "js-template"]
460
+ }
461
+ ]
462
+ }), placeholders(icu)]
463
+ };
464
+ }
465
+ function svelteI18n() {
466
+ return {
467
+ name: "svelte-i18n",
468
+ detect: dependsOn([
469
+ "svelte-i18n",
470
+ "sveltekit-i18n",
471
+ "@sveltekit-i18n/base"
472
+ ]),
473
+ plugins: [library({
474
+ name: "svelte-i18n",
475
+ calls: [{ callee: [
476
+ "$_",
477
+ "$t",
478
+ "$format",
479
+ "_",
480
+ "t",
481
+ "format",
482
+ "$json",
483
+ "json"
484
+ ] }],
485
+ templates: [{
486
+ template: "{$_('{key}'{args})}",
487
+ kinds: ["svelte-text"],
488
+ imports: ["import { _ } from 'svelte-i18n'"]
489
+ }, {
490
+ template: "{attr}={$_('{key}'{args})}",
491
+ kinds: ["svelte-attribute"],
492
+ imports: ["import { _ } from 'svelte-i18n'"]
493
+ }]
494
+ }), placeholders(icu)]
495
+ };
496
+ }
497
+ function vscodeExtension() {
498
+ return {
499
+ name: "vscode-extension",
500
+ detect: (ctx) => ctx.hasAnyDependency(["vscode", "@types/vscode"]),
501
+ plugins: [library({
502
+ name: "vscode-extension",
503
+ calls: [{ callee: ["l10n.t", "vscode.l10n.t"] }],
504
+ finders: [nlsFinder],
505
+ extra: (draft) => {
506
+ draft.usage = {
507
+ ...draft.usage,
508
+ regex: [...draft.usage.regex, {
509
+ extensions: ["json"],
510
+ patterns: ["\"%({key})%\""]
511
+ }]
512
+ };
513
+ }
514
+ }), placeholders(braces)]
515
+ };
516
+ }
517
+ /** Always last: hard-string extraction templates for plain JS, TS and HTML. */
518
+ function general() {
519
+ return {
520
+ name: "general",
521
+ detect: () => true,
522
+ plugins: [{
523
+ name: "x18n:general",
524
+ extractors: [{
525
+ id: "general",
526
+ templates: defaultTemplates
527
+ }]
528
+ }]
529
+ };
530
+ }
531
+ //#endregion
532
+ //#region src/presets/mobile.ts
533
+ /** Flutter with `flutter_i18n`: JSON or YAML assets, `FlutterI18n.translate(context, 'key')`. */
534
+ function flutter() {
535
+ return {
536
+ name: "flutter",
537
+ detect: dependsOn(["flutter_i18n"]),
538
+ plugins: [library({
539
+ name: "flutter",
540
+ regex: [{
541
+ extensions: ["dart"],
542
+ patterns: ["[^\\w\\d]FlutterI18n\\.(?:plural|translate)\\(\\s*[\\w\\d.]+,\\s*['\"`]({key})['\"`]", "[^\\w\\d]I18n(?:Plural|Text)\\(\\s*['\"`]({key})['\"`]"]
543
+ }],
544
+ resources: [{ pattern: "assets/flutter_i18n/{locale}.{ext}" }, { pattern: "assets/i18n/{locale}.{ext}" }],
545
+ templates: [{ template: "FlutterI18n.translate(context, '{key}')" }]
546
+ }), placeholders(braces)]
547
+ };
548
+ }
549
+ //#endregion
550
+ //#region src/presets/server.ts
551
+ /** Laravel: `lang/{locale}/{file}.php` arrays and `lang/{locale}.json`; keys are `file.key`. Exclusive like i18n-ally. */
552
+ function laravel() {
553
+ return {
554
+ name: "laravel",
555
+ detect: dependsOn(["laravel/framework"]),
556
+ exclusive: true,
557
+ plugins: [library({
558
+ name: "laravel",
559
+ regex: [{
560
+ extensions: ["php"],
561
+ patterns: ["[^\\w\\d](?:__|trans|@lang|trans_choice|Lang::get|Lang::choice)\\(\\s*'([^']+)'", "[^\\w\\d](?:__|trans|@lang|trans_choice|Lang::get|Lang::choice)\\(\\s*\"([^\"]+)\""]
562
+ }],
563
+ resources: [
564
+ { pattern: "lang/{locale}/{namespaces}.php" },
565
+ { pattern: "resources/lang/{locale}/{namespaces}.php" },
566
+ { pattern: "lang/{locale}.json" },
567
+ { pattern: "resources/lang/{locale}.json" }
568
+ ],
569
+ normalizeKey: (raw) => raw.includes("/") ? raw.replace(/\//g, ".") : void 0,
570
+ templates: [{ template: "__('{key}')" }]
571
+ }), placeholders(colon)]
572
+ };
573
+ }
574
+ /** Joomla: INI language files, `Text::_('KEY')`. */
575
+ function phpJoomla() {
576
+ return {
577
+ name: "php-joomla",
578
+ detect: dependsOn(["joomla/application", "joomla/cms"]),
579
+ plugins: [library({
580
+ name: "php-joomla",
581
+ regex: [{
582
+ extensions: ["php"],
583
+ patterns: ["J?Text::(?:_|[\\w]+)\\(\\s*['\"]({key})['\"]"]
584
+ }],
585
+ resources: [{ pattern: "language/{locale}/*.ini" }, { pattern: "administrator/language/{locale}/*.ini" }],
586
+ keyStyle: "flat",
587
+ templates: [{ template: "Text::_('{key}')" }]
588
+ }), placeholders(printf)]
589
+ };
590
+ }
591
+ /** PHP gettext with `.po` files. Exclusive and never detected, as in i18n-ally: list it in `presets`. */
592
+ function phpGettext() {
593
+ return {
594
+ name: "php-gettext",
595
+ exclusive: true,
596
+ plugins: [library({
597
+ name: "php-gettext",
598
+ regex: [{
599
+ extensions: ["php"],
600
+ patterns: ["[^\\w\\d](?:gettext|_|__)\\(\\s*['\"`]({key})['\"`]"]
601
+ }],
602
+ resources: [{ pattern: "locale/{locale}/LC_MESSAGES/*.po" }, { pattern: "locales/{locale}.po" }],
603
+ extra: (draft) => {
604
+ draft.extract = {
605
+ keygen: "source",
606
+ ...draft.extract
607
+ };
608
+ },
609
+ templates: [{ template: "gettext('{key}')" }]
610
+ }), placeholders(printf)]
611
+ };
612
+ }
613
+ const viewSuffix = /(?:\.html)?\.(?:erb|haml|slim)$/;
614
+ /** `t('.title')` in `app/views/users/index.html.erb` means `users.index.title`. */
615
+ function railsLazyKey(raw, path) {
616
+ if (!raw.startsWith(".")) return void 0;
617
+ const match = /(?:^|\/)app\/views\/(.+)$/.exec(path);
618
+ if (!match) return void 0;
619
+ return `${match[1].replace(viewSuffix, "").split("/").map((segment) => segment.replace(/^_/, "")).join(".")}${raw}`;
620
+ }
621
+ function rubyRails() {
622
+ return {
623
+ name: "ruby-rails",
624
+ detect: dependsOn([
625
+ "rails",
626
+ "rails-i18n",
627
+ "railties"
628
+ ]),
629
+ plugins: [library({
630
+ name: "ruby-rails",
631
+ regex: [{
632
+ extensions: [
633
+ "rb",
634
+ "erb",
635
+ "haml",
636
+ "slim"
637
+ ],
638
+ patterns: [
639
+ "[^\\w\\d]t(?:\\(| )\\s*['\"`]({key})['\"`]",
640
+ "[^\\w\\d]t\\(:({key})\\)",
641
+ "I18n\\.t\\(\\s*['\"`]({key})['\"`]"
642
+ ]
643
+ }],
644
+ resources: [
645
+ { pattern: "config/locales/{locale}.yml" },
646
+ { pattern: "config/locales/**/*.{locale}.yml" },
647
+ { pattern: "config/locales/{locale}/**/*.yml" }
648
+ ],
649
+ normalizeKey: (raw, ctx) => railsLazyKey(raw, ctx.path),
650
+ transformResource: {
651
+ applies: (ctx) => /\.ya?ml$/.test(ctx.path),
652
+ read(tree, ctx) {
653
+ const root = tree[ctx.locale];
654
+ return root !== void 0 && !isMessageLeaf(root) ? root : {};
655
+ },
656
+ toStored: (path, ctx) => [ctx.locale, ...path]
657
+ },
658
+ templates: [{ template: "t('{key}')" }]
659
+ }), placeholders(percentBraces)]
660
+ };
661
+ }
662
+ function jekyll() {
663
+ return {
664
+ name: "jekyll",
665
+ detect: dependsOn(["jekyll-multiple-languages-plugin"]),
666
+ plugins: [library({
667
+ name: "jekyll",
668
+ regex: [{
669
+ extensions: [
670
+ "html",
671
+ "md",
672
+ "liquid"
673
+ ],
674
+ patterns: ["\\{%\\s+t\\s+({key})\\s+%\\}"]
675
+ }],
676
+ resources: [{ pattern: "_i18n/{locale}.yml" }],
677
+ keyStyle: "nested",
678
+ templates: [{ template: "{% t {key} %}" }]
679
+ })]
680
+ };
681
+ }
682
+ //#endregion
683
+ //#region src/presets/web.ts
684
+ const jsxTemplates = [
685
+ {
686
+ template: "{t('{key}'{args})}",
687
+ kinds: ["jsx-text"]
688
+ },
689
+ {
690
+ template: "{attr}={t('{key}'{args})}",
691
+ kinds: ["jsx-attribute"]
692
+ },
693
+ {
694
+ template: "t('{key}'{args})",
695
+ kinds: ["js-string", "js-template"]
696
+ }
697
+ ];
698
+ function nextInternational() {
699
+ return {
700
+ name: "next-international",
701
+ detect: dependsOn(["next-international"]),
702
+ plugins: [library({
703
+ name: "next-international",
704
+ calls: [{
705
+ callee: ["useI18n", "getI18n"],
706
+ factory: true,
707
+ returns: ["t"]
708
+ }, {
709
+ callee: ["useScopedI18n", "getScopedI18n"],
710
+ factory: true,
711
+ returns: ["t"],
712
+ keyPrefix: { arg: 0 }
713
+ }],
714
+ resources: [{ pattern: "locales/{locale}.{ext}" }, { pattern: "src/locales/{locale}.{ext}" }],
715
+ templates: jsxTemplates
716
+ }), placeholders(braces)]
717
+ };
718
+ }
719
+ function nextTranslate() {
720
+ return {
721
+ name: "next-translate",
722
+ detect: dependsOn(["next-translate"]),
723
+ plugins: [library({
724
+ name: "next-translate",
725
+ namespace: {
726
+ separator: ":",
727
+ default: "common"
728
+ },
729
+ calls: [
730
+ {
731
+ callee: "useTranslation",
732
+ factory: true,
733
+ returns: ["t"],
734
+ namespace: { arg: 0 }
735
+ },
736
+ {
737
+ callee: "getT",
738
+ factory: true,
739
+ returns: ["t"],
740
+ namespace: { arg: 1 }
741
+ },
742
+ {
743
+ callee: "Trans",
744
+ key: {
745
+ arg: 0,
746
+ property: "i18nKey"
747
+ },
748
+ namespace: {
749
+ arg: 0,
750
+ property: "ns"
751
+ }
752
+ },
753
+ { callee: "t" }
754
+ ],
755
+ regex: [{
756
+ extensions: scriptExtensions,
757
+ patterns: ["[^\\w\\d]t`({key})`"]
758
+ }],
759
+ resources: [{ pattern: "locales/{locale}/{namespace}.json" }],
760
+ keyStyle: "nested",
761
+ templates: jsxTemplates
762
+ }), placeholders(doubleBraces)]
763
+ };
764
+ }
765
+ /** Shopify's i18next format: plural and ordinal forms as nested keys (`{key}.one`). */
766
+ function i18nextShopify() {
767
+ const forms = [
768
+ "plural",
769
+ "zero",
770
+ "one",
771
+ "two",
772
+ "few",
773
+ "many",
774
+ "other",
775
+ ...Array.from({ length: 10 }, (_, i) => String(i))
776
+ ];
777
+ return {
778
+ name: "i18next-shopify",
779
+ detect: dependsOn(["@shopify/i18next-shopify"]),
780
+ plugins: [
781
+ ...reactI18next().plugins,
782
+ library({
783
+ name: "i18next-shopify",
784
+ derivedKeys: forms.map((form) => `{key}.${form}`),
785
+ keyStyle: "nested"
786
+ }),
787
+ placeholders(braces)
788
+ ]
789
+ };
790
+ }
791
+ function i18nTag() {
792
+ return {
793
+ name: "i18n-tag",
794
+ detect: dependsOn(["es2015-i18n-tag"]),
795
+ plugins: [library({
796
+ name: "i18n-tag",
797
+ regex: [{
798
+ extensions: [...scriptExtensions, "vue"],
799
+ patterns: ["i18n`({key})`"]
800
+ }],
801
+ templates: [{
802
+ template: "i18n`{key}`",
803
+ kinds: ["js-string", "js-template"]
804
+ }]
805
+ })]
806
+ };
807
+ }
808
+ function fluentVue() {
809
+ return {
810
+ name: "fluent-vue",
811
+ detect: dependsOn([
812
+ "fluent-vue",
813
+ "fluent-vue-loader",
814
+ "rollup-plugin-fluent-vue",
815
+ "unplugin-fluent-vue"
816
+ ]),
817
+ plugins: [
818
+ library({
819
+ name: "fluent-vue",
820
+ calls: [{ callee: [
821
+ "$t",
822
+ "$ta",
823
+ "fluent.format",
824
+ "fluent.formatAttrs",
825
+ "this.$t",
826
+ "this.$ta"
827
+ ] }, {
828
+ callee: "useFluent",
829
+ factory: true,
830
+ returns: [
831
+ "$t",
832
+ "$ta",
833
+ "format",
834
+ "formatAttrs"
835
+ ]
836
+ }],
837
+ regex: [{
838
+ extensions: ["vue"],
839
+ patterns: ["v-t:({key})[\\s\\n=>]", "<i18n[\\s\\n]+path=['\"`]({key})['\"`]"]
840
+ }],
841
+ resources: [{ pattern: "locales/{locale}.ftl" }, { pattern: "src/locales/{locale}.ftl" }],
842
+ templates: [
843
+ {
844
+ template: "{{ $t('{key}'{args}) }}",
845
+ kinds: ["vue-template-text"]
846
+ },
847
+ {
848
+ template: ":{attr}=\"$t('{key}'{args})\"",
849
+ kinds: ["vue-attribute"]
850
+ },
851
+ {
852
+ template: "$t('{key}'{args})",
853
+ kinds: ["js-string", "js-template"]
854
+ }
855
+ ]
856
+ }),
857
+ sfcFluentBlocks(),
858
+ placeholders(fluent)
859
+ ]
860
+ };
861
+ }
862
+ function polyglot() {
863
+ return {
864
+ name: "polyglot",
865
+ detect: dependsOn(["node-polyglot"]),
866
+ plugins: [library({
867
+ name: "polyglot",
868
+ calls: [{ callee: ["polyglot.t", "I18n.t"] }],
869
+ regex: [{
870
+ extensions: ["rb"],
871
+ patterns: ["(?:polyglot|I18n)\\.t\\(\\s*['\"`]({key})['\"`]"]
872
+ }],
873
+ templates: [{
874
+ template: "polyglot.t('{key}'{args})",
875
+ kinds: ["js-string", "js-template"]
876
+ }]
877
+ }), placeholders(percentBraces)]
878
+ };
879
+ }
880
+ function globalize() {
881
+ return {
882
+ name: "globalize",
883
+ detect: dependsOn(["globalize"]),
884
+ plugins: [library({
885
+ name: "globalize",
886
+ calls: [{ callee: [
887
+ "Globalize.messageFormatter",
888
+ "Globalize.formatMessage",
889
+ "globalize.messageFormatter",
890
+ "globalize.formatMessage"
891
+ ] }],
892
+ normalizeKey: (raw) => raw.includes("/") ? raw.replace(/\//g, ".") : void 0,
893
+ templates: [{
894
+ template: "Globalize.formatMessage('{key}'{args})",
895
+ kinds: ["js-string", "js-template"]
896
+ }]
897
+ }), placeholders(icu)]
898
+ };
899
+ }
900
+ function ui5() {
901
+ return {
902
+ name: "ui5",
903
+ detect: dependsOn(["@ui5/cli"]),
904
+ plugins: [library({
905
+ name: "ui5",
906
+ regex: [{
907
+ extensions: [
908
+ "xml",
909
+ "json",
910
+ ...scriptExtensions
911
+ ],
912
+ patterns: ["\\{i18n>({key})\\}"]
913
+ }, {
914
+ extensions: scriptExtensions,
915
+ patterns: ["getResourceBundle\\(\\)\\.getText\\(\\s*['\"`]({key})['\"`]"]
916
+ }],
917
+ resources: [{ pattern: "webapp/i18n/i18n_{locale}.properties" }, { pattern: "webapp/i18n/i18n.properties" }],
918
+ keyStyle: "flat",
919
+ templates: [{
920
+ template: "{i18n>{key}}",
921
+ files: ["**/*.xml"]
922
+ }, {
923
+ template: "this.getResourceBundle().getText('{key}')",
924
+ kinds: ["js-string", "js-template"]
925
+ }]
926
+ }), placeholders(braces)]
927
+ };
928
+ }
929
+ function lingui() {
930
+ return {
931
+ name: "lingui",
932
+ detect: dependsOn([
933
+ "@lingui/core",
934
+ "@lingui/react",
935
+ "@lingui/macro"
936
+ ]),
937
+ plugins: [library({
938
+ name: "lingui",
939
+ calls: [
940
+ { callee: [
941
+ "i18n._",
942
+ "i18n.t",
943
+ "t",
944
+ "_"
945
+ ] },
946
+ {
947
+ callee: [
948
+ "t",
949
+ "msg",
950
+ "defineMessage",
951
+ "i18n._",
952
+ "Trans"
953
+ ],
954
+ key: {
955
+ arg: 0,
956
+ property: "id"
957
+ }
958
+ },
959
+ {
960
+ callee: "useLingui",
961
+ factory: true,
962
+ returns: ["t", "_"]
963
+ }
964
+ ],
965
+ regex: [{
966
+ extensions: scriptExtensions,
967
+ patterns: ["\\bt`({key})`", "<Trans>[\\s]*({key})[\\s]*<\\/Trans>"]
968
+ }],
969
+ resources: [{ pattern: "src/locales/{locale}/messages.po" }, { pattern: "locales/{locale}/messages.po" }],
970
+ extra: (draft) => {
971
+ draft.extract = {
972
+ keygen: "source",
973
+ ...draft.extract
974
+ };
975
+ },
976
+ templates: [{
977
+ template: "<Trans>{key}</Trans>",
978
+ kinds: ["jsx-text"],
979
+ imports: ["import { Trans } from '@lingui/react/macro'"]
980
+ }, {
981
+ template: "t`{key}`",
982
+ kinds: ["js-string", "js-template"],
983
+ imports: ["import { t } from '@lingui/core/macro'"]
984
+ }]
985
+ }), placeholders(icu)]
986
+ };
987
+ }
988
+ function emberIntl() {
989
+ return {
990
+ name: "ember-intl",
991
+ detect: dependsOn(["ember-intl"]),
992
+ plugins: [library({
993
+ name: "ember-intl",
994
+ calls: [{ callee: ["intl.t"] }],
995
+ regex: [{
996
+ extensions: [
997
+ "hbs",
998
+ "gjs",
999
+ "gts"
1000
+ ],
1001
+ patterns: ["\\{\\{\\s*t\\s+['\"`]({key})['\"`]", "\\(\\s*t\\s+['\"`]({key})['\"`]"]
1002
+ }],
1003
+ resources: [{ pattern: "translations/{locale}.{ext}" }, { pattern: "translations/{namespaces}/{locale}.{ext}" }],
1004
+ templates: [{
1005
+ template: "{{t '{key}'}}",
1006
+ files: ["**/*.hbs"]
1007
+ }, {
1008
+ template: "this.intl.t('{key}'{args})",
1009
+ kinds: ["js-string", "js-template"]
1010
+ }]
1011
+ }), placeholders(icu)]
1012
+ };
1013
+ }
1014
+ function i18nJs() {
1015
+ return {
1016
+ name: "i18n-js",
1017
+ detect: dependsOn(["i18n-js"]),
1018
+ plugins: [library({
1019
+ name: "i18n-js",
1020
+ calls: [{ callee: ["I18n.t", "i18n.t"] }],
1021
+ templates: [{
1022
+ template: "i18n.t('{key}'{args})",
1023
+ kinds: ["js-string", "js-template"]
1024
+ }]
1025
+ }), placeholders(percentBraces)]
1026
+ };
1027
+ }
1028
+ /** Chrome and WebExtension `_locales/{locale}/messages.json`: `{ "name": { "message": "..." } }` reads as `name`. */
1029
+ function chromeExtension() {
1030
+ return {
1031
+ name: "chrome-extension",
1032
+ async detect(ctx) {
1033
+ for (const path of [
1034
+ "manifest.json",
1035
+ "src/manifest.json",
1036
+ "public/manifest.json"
1037
+ ]) if ((await ctx.host.readFile(path))?.includes("\"default_locale\"")) return true;
1038
+ return false;
1039
+ },
1040
+ plugins: [
1041
+ library({
1042
+ name: "chrome-extension",
1043
+ calls: [{ callee: [
1044
+ "chrome.i18n.getMessage",
1045
+ "browser.i18n.getMessage",
1046
+ "i18n.getMessage"
1047
+ ] }],
1048
+ regex: [{
1049
+ extensions: [
1050
+ "json",
1051
+ "html",
1052
+ "css"
1053
+ ],
1054
+ patterns: ["__MSG_({key})__"]
1055
+ }],
1056
+ resources: [{ pattern: "_locales/{locale}/messages.json" }, { pattern: "{src,public,extension}/_locales/{locale}/messages.json" }],
1057
+ transformResource: {
1058
+ applies: (ctx) => /(?:^|\/)_locales\/[^/]+\/messages\.json$/.test(ctx.path),
1059
+ read(tree) {
1060
+ const out = {};
1061
+ for (const [name, entry] of Object.entries(tree)) {
1062
+ const message = isMessageLeaf(entry) ? void 0 : entry.message;
1063
+ if (message !== void 0 && isMessageLeaf(message)) out[name] = message;
1064
+ }
1065
+ return out;
1066
+ },
1067
+ toStored: (path) => [path.join("."), "message"]
1068
+ },
1069
+ templates: [{
1070
+ template: "chrome.i18n.getMessage('{key}')",
1071
+ kinds: ["js-string", "js-template"]
1072
+ }]
1073
+ }),
1074
+ {
1075
+ name: "x18n:chrome-extension-source-locale",
1076
+ async config(draft, ctx) {
1077
+ const manifest = await ctx.host.readFile("manifest.json") ?? await ctx.host.readFile("src/manifest.json") ?? await ctx.host.readFile("public/manifest.json");
1078
+ const locale = manifest ? /"default_locale"\s*:\s*"([^"]+)"/.exec(manifest)?.[1] : void 0;
1079
+ if (locale) draft.sourceLocale = locale;
1080
+ }
1081
+ },
1082
+ placeholders(dollars)
1083
+ ]
1084
+ };
1085
+ }
1086
+ //#endregion
1087
+ //#region src/presets/index.ts
1088
+ /**
1089
+ * Presets by name, in detection order: every i18n-ally framework has one.
1090
+ * Presets that share plugins (react-i18next and i18next) apply them once.
1091
+ */
1092
+ const presets = {
1093
+ "nuxt-i18n": () => nuxtI18n(),
1094
+ "vue-i18n": () => vueI18n(),
1095
+ "fluent-vue": () => fluentVue(),
1096
+ "i18next-shopify": () => i18nextShopify(),
1097
+ "react-i18next": () => reactI18next(),
1098
+ "next-translate": () => nextTranslate(),
1099
+ "next-international": () => nextInternational(),
1100
+ "i18next": () => i18next(),
1101
+ "next-intl": () => nextIntl(),
1102
+ "react-intl": () => reactIntl(),
1103
+ "svelte-i18n": () => svelteI18n(),
1104
+ "ngx-translate": () => ngxTranslate(),
1105
+ "transloco": () => transloco(),
1106
+ "lingui": () => lingui(),
1107
+ "ember-intl": () => emberIntl(),
1108
+ "polyglot": () => polyglot(),
1109
+ "globalize": () => globalize(),
1110
+ "i18n-tag": () => i18nTag(),
1111
+ "i18n-js": () => i18nJs(),
1112
+ "ui5": () => ui5(),
1113
+ "chrome-extension": () => chromeExtension(),
1114
+ "vscode-extension": () => vscodeExtension(),
1115
+ "laravel": () => laravel(),
1116
+ "php-joomla": () => phpJoomla(),
1117
+ "php-gettext": () => phpGettext(),
1118
+ "ruby-rails": () => rubyRails(),
1119
+ "jekyll": () => jekyll(),
1120
+ "flutter": () => flutter(),
1121
+ "general": () => general()
1122
+ };
1123
+ //#endregion
1124
+ export { ngxTranslate as A, nextIntl as C, svelteI18n as D, reactIntl as E, vscodeExtension as O, i18next as S, reactI18next as T, phpJoomla as _, globalize as a, flutter as b, i18nextShopify as c, nextTranslate as d, polyglot as f, phpGettext as g, laravel as h, fluentVue as i, transloco as j, vueI18n as k, lingui as l, jekyll as m, chromeExtension as n, i18nJs as o, ui5 as p, emberIntl as r, i18nTag as s, presets as t, nextInternational as u, railsLazyKey as v, nuxtI18n as w, general as x, rubyRails as y };