@intlayer/core 7.3.15 → 7.5.0-canary.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.
Files changed (56) hide show
  1. package/dist/cjs/index.cjs +3 -1
  2. package/dist/cjs/localization/getLocalizedUrl.cjs +8 -2
  3. package/dist/cjs/localization/getLocalizedUrl.cjs.map +1 -1
  4. package/dist/cjs/localization/getMultilingualUrls.cjs +7 -1
  5. package/dist/cjs/localization/getMultilingualUrls.cjs.map +1 -1
  6. package/dist/cjs/localization/getPrefix.cjs +8 -2
  7. package/dist/cjs/localization/getPrefix.cjs.map +1 -1
  8. package/dist/cjs/localization/index.cjs +3 -1
  9. package/dist/cjs/localization/validatePrefix.cjs +54 -0
  10. package/dist/cjs/localization/validatePrefix.cjs.map +1 -0
  11. package/dist/cjs/messageFormat/ICU.cjs +309 -0
  12. package/dist/cjs/messageFormat/ICU.cjs.map +1 -0
  13. package/dist/cjs/messageFormat/i18next.cjs +320 -0
  14. package/dist/cjs/messageFormat/i18next.cjs.map +1 -0
  15. package/dist/cjs/messageFormat/index.cjs +10 -0
  16. package/dist/cjs/messageFormat/vue-i18n.cjs +166 -0
  17. package/dist/cjs/messageFormat/vue-i18n.cjs.map +1 -0
  18. package/dist/esm/index.mjs +2 -1
  19. package/dist/esm/localization/getLocalizedUrl.mjs +8 -2
  20. package/dist/esm/localization/getLocalizedUrl.mjs.map +1 -1
  21. package/dist/esm/localization/getMultilingualUrls.mjs +7 -1
  22. package/dist/esm/localization/getMultilingualUrls.mjs.map +1 -1
  23. package/dist/esm/localization/getPrefix.mjs +8 -2
  24. package/dist/esm/localization/getPrefix.mjs.map +1 -1
  25. package/dist/esm/localization/index.mjs +2 -1
  26. package/dist/esm/localization/validatePrefix.mjs +52 -0
  27. package/dist/esm/localization/validatePrefix.mjs.map +1 -0
  28. package/dist/esm/messageFormat/ICU.mjs +307 -0
  29. package/dist/esm/messageFormat/ICU.mjs.map +1 -0
  30. package/dist/esm/messageFormat/i18next.mjs +318 -0
  31. package/dist/esm/messageFormat/i18next.mjs.map +1 -0
  32. package/dist/esm/messageFormat/index.mjs +5 -0
  33. package/dist/esm/messageFormat/vue-i18n.mjs +164 -0
  34. package/dist/esm/messageFormat/vue-i18n.mjs.map +1 -0
  35. package/dist/types/deepTransformPlugins/getFilterMissingTranslationsContent.d.ts +1 -0
  36. package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts +8 -7
  37. package/dist/types/deepTransformPlugins/getFilterTranslationsOnlyContent.d.ts.map +1 -1
  38. package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts +8 -7
  39. package/dist/types/deepTransformPlugins/getFilteredLocalesContent.d.ts.map +1 -1
  40. package/dist/types/dictionaryManipulator/orderDictionaries.d.ts +2 -2
  41. package/dist/types/index.d.ts +2 -1
  42. package/dist/types/localization/getMultilingualUrls.d.ts.map +1 -1
  43. package/dist/types/localization/getPrefix.d.ts +2 -1
  44. package/dist/types/localization/getPrefix.d.ts.map +1 -1
  45. package/dist/types/localization/index.d.ts +2 -1
  46. package/dist/types/localization/validatePrefix.d.ts +38 -0
  47. package/dist/types/localization/validatePrefix.d.ts.map +1 -0
  48. package/dist/types/messageFormat/ICU.d.ts +11 -0
  49. package/dist/types/messageFormat/ICU.d.ts.map +1 -0
  50. package/dist/types/messageFormat/i18next.d.ts +9 -0
  51. package/dist/types/messageFormat/i18next.d.ts.map +1 -0
  52. package/dist/types/messageFormat/index.d.ts +4 -0
  53. package/dist/types/messageFormat/vue-i18n.d.ts +9 -0
  54. package/dist/types/messageFormat/vue-i18n.d.ts.map +1 -0
  55. package/dist/types/transpiler/enumeration/enumeration.d.ts.map +1 -1
  56. package/package.json +11 -6
@@ -1,4 +1,5 @@
1
1
  import { getLocalizedUrl } from "./getLocalizedUrl.mjs";
2
+ import { DefaultValues } from "@intlayer/config/client";
2
3
  import configuration from "@intlayer/config/built";
3
4
 
4
5
  //#region src/localization/getMultilingualUrls.ts
@@ -37,7 +38,12 @@ import configuration from "@intlayer/config/built";
37
38
  * @returns An object mapping each locale to its corresponding multilingual URL.
38
39
  */
39
40
  const getMultilingualUrls = (url, options = {}) => {
40
- const { locales = configuration?.internationalization?.locales, defaultLocale = configuration?.internationalization?.defaultLocale, mode = configuration?.routing?.mode } = options;
41
+ const { defaultLocale, mode, locales } = {
42
+ defaultLocale: configuration?.internationalization?.defaultLocale ?? DefaultValues.Internationalization.DEFAULT_LOCALE,
43
+ mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,
44
+ locales: configuration?.internationalization?.locales ?? DefaultValues.Internationalization.LOCALES,
45
+ ...options
46
+ };
41
47
  return (locales ?? []).reduce((acc, locale) => {
42
48
  acc[locale] = getLocalizedUrl(url, locale, {
43
49
  locales,
@@ -1 +1 @@
1
- {"version":3,"file":"getMultilingualUrls.mjs","names":[],"sources":["../../../src/localization/getMultilingualUrls.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport type { LocalesValues, StrictModeLocaleMap } from '@intlayer/types';\nimport { getLocalizedUrl } from './getLocalizedUrl';\n\n/**\n * Generates multilingual URLs by prefixing the given URL with each supported locale\n * or adding search parameters based on the routing mode.\n * Handles both absolute and relative URLs appropriately.\n *\n * This function gets the locales, default locale, and routing mode from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { en: '/dashboard', fr: '/fr/dashboard' }\n *\n * // prefix-all mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { en: '/en/dashboard', fr: '/fr/dashboard' }\n *\n * // search-params mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'search-params' })\n * // Returns { en: '/dashboard?locale=en', fr: '/dashboard?locale=fr' }\n *\n * // no-prefix mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { en: '/dashboard', fr: '/dashboard' }\n * ```\n *\n * @param url - The original URL string to be processed.\n * @param options - Configuration options\n * @param options.locales - Optional array of supported locales. Defaults to configured locales.\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object mapping each locale to its corresponding multilingual URL.\n */\nexport const getMultilingualUrls = (\n url: string,\n options: {\n locales?: LocalesValues[];\n defaultLocale?: LocalesValues;\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n } = {}\n): StrictModeLocaleMap<string> => {\n const {\n locales = configuration?.internationalization?.locales,\n defaultLocale = configuration?.internationalization?.defaultLocale,\n mode = configuration?.routing?.mode,\n } = options;\n\n // Generate multilingual URLs by iterating over each locale and calling getLocalizedUrl\n const multilingualUrls = (locales ?? []).reduce<StrictModeLocaleMap<string>>(\n (acc, locale) => {\n // Get the localized URL for this locale\n const localizedUrl = getLocalizedUrl(url, locale, {\n locales,\n defaultLocale,\n mode,\n });\n\n // Assign the constructed URL to the corresponding locale key\n acc[locale as unknown as keyof typeof acc] = localizedUrl;\n\n return acc;\n },\n {} as StrictModeLocaleMap<string>\n );\n\n return multilingualUrls;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAa,uBACX,KACA,UAII,EAAE,KAC0B;CAChC,MAAM,EACJ,UAAU,eAAe,sBAAsB,SAC/C,gBAAgB,eAAe,sBAAsB,eACrD,OAAO,eAAe,SAAS,SAC7B;AAoBJ,SAjB0B,WAAW,EAAE,EAAE,QACtC,KAAK,WAAW;AASf,MAAI,UAPiB,gBAAgB,KAAK,QAAQ;GAChD;GACA;GACA;GACD,CAAC;AAKF,SAAO;IAET,EAAE,CACH"}
1
+ {"version":3,"file":"getMultilingualUrls.mjs","names":[],"sources":["../../../src/localization/getMultilingualUrls.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport { DefaultValues } from '@intlayer/config/client';\nimport type { LocalesValues, StrictModeLocaleMap } from '@intlayer/types';\nimport { getLocalizedUrl } from './getLocalizedUrl';\n\n/**\n * Generates multilingual URLs by prefixing the given URL with each supported locale\n * or adding search parameters based on the routing mode.\n * Handles both absolute and relative URLs appropriately.\n *\n * This function gets the locales, default locale, and routing mode from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { en: '/dashboard', fr: '/fr/dashboard' }\n *\n * // prefix-all mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { en: '/en/dashboard', fr: '/fr/dashboard' }\n *\n * // search-params mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'search-params' })\n * // Returns { en: '/dashboard?locale=en', fr: '/dashboard?locale=fr' }\n *\n * // no-prefix mode\n * getMultilingualUrls('/dashboard', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { en: '/dashboard', fr: '/dashboard' }\n * ```\n *\n * @param url - The original URL string to be processed.\n * @param options - Configuration options\n * @param options.locales - Optional array of supported locales. Defaults to configured locales.\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object mapping each locale to its corresponding multilingual URL.\n */\nexport const getMultilingualUrls = (\n url: string,\n options: {\n locales?: LocalesValues[];\n defaultLocale?: LocalesValues;\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n } = {}\n): StrictModeLocaleMap<string> => {\n const { defaultLocale, mode, locales } = {\n defaultLocale:\n configuration?.internationalization?.defaultLocale ??\n DefaultValues.Internationalization.DEFAULT_LOCALE,\n mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,\n locales:\n configuration?.internationalization?.locales ??\n DefaultValues.Internationalization.LOCALES,\n ...options,\n };\n\n // Generate multilingual URLs by iterating over each locale and calling getLocalizedUrl\n const multilingualUrls = (locales ?? []).reduce<StrictModeLocaleMap<string>>(\n (acc, locale) => {\n // Get the localized URL for this locale\n const localizedUrl = getLocalizedUrl(url, locale, {\n locales,\n defaultLocale,\n mode,\n });\n\n // Assign the constructed URL to the corresponding locale key\n acc[locale as unknown as keyof typeof acc] = localizedUrl;\n\n return acc;\n },\n {} as StrictModeLocaleMap<string>\n );\n\n return multilingualUrls;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAa,uBACX,KACA,UAII,EAAE,KAC0B;CAChC,MAAM,EAAE,eAAe,MAAM,YAAY;EACvC,eACE,eAAe,sBAAsB,iBACrC,cAAc,qBAAqB;EACrC,MAAM,eAAe,SAAS,QAAQ,cAAc,QAAQ;EAC5D,SACE,eAAe,sBAAsB,WACrC,cAAc,qBAAqB;EACrC,GAAG;EACJ;AAoBD,SAjB0B,WAAW,EAAE,EAAE,QACtC,KAAK,WAAW;AASf,MAAI,UAPiB,gBAAgB,KAAK,QAAQ;GAChD;GACA;GACA;GACD,CAAC;AAKF,SAAO;IAET,EAAE,CACH"}
@@ -1,3 +1,4 @@
1
+ import { DefaultValues } from "@intlayer/config/client";
1
2
  import configuration from "@intlayer/config/built";
2
3
 
3
4
  //#region src/localization/getPrefix.ts
@@ -35,8 +36,13 @@ import configuration from "@intlayer/config/built";
35
36
  * @returns An object containing pathPrefix, prefix, and localePrefix for the given locale.
36
37
  */
37
38
  const getPrefix = (locale, options = {}) => {
38
- const { defaultLocale = configuration?.internationalization?.defaultLocale, mode = configuration?.routing?.mode } = options;
39
- if (!locale) return {
39
+ const { defaultLocale, mode, locales } = {
40
+ defaultLocale: configuration?.internationalization?.defaultLocale ?? DefaultValues.Internationalization.DEFAULT_LOCALE,
41
+ mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,
42
+ locales: configuration?.internationalization?.locales ?? DefaultValues.Internationalization.LOCALES,
43
+ ...options
44
+ };
45
+ if (!locale || !locales.includes(locale)) return {
40
46
  prefix: "",
41
47
  localePrefix: void 0
42
48
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getPrefix.mjs","names":[],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport type { Locale, LocalesValues } from '@intlayer/types';\n\nexport type GetPrefixOptions = {\n defaultLocale?: LocalesValues;\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n};\n\nexport type GetPrefixResult = {\n /**\n * The complete base URL path with leading and trailing slashes.\n *\n * @example\n * // https://example.com/fr/about -> '/fr'\n * // https://example.com/about -> ''\n */\n prefix: string;\n /**\n * The locale identifier without slashes.\n *\n * @example\n * // https://example.com/fr/about -> 'fr'\n * // https://example.com/about -> undefined\n */\n localePrefix: Locale | undefined;\n};\n\n/**\n * Determines the URL prefix for a given locale based on the routing mode configuration.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode with default locale\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // prefix-no-default mode with non-default locale\n * getPrefix('fr', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '/fr', localePrefix: 'fr' }\n *\n * // prefix-all mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { prefix: '/en', localePrefix: locale }\n *\n * // search-params mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'search-params' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // no-prefix mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { prefix: '', localePrefix: undefined }\n * ```\n *\n * @param locale - The locale to check for prefix. If not provided, uses configured default locale.\n * @param options - Configuration options\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object containing pathPrefix, prefix, and localePrefix for the given locale.\n */\nexport const getPrefix = (\n locale: LocalesValues,\n options: {\n defaultLocale?: LocalesValues;\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n } = {}\n): GetPrefixResult => {\n const {\n defaultLocale = configuration?.internationalization?.defaultLocale,\n mode = configuration?.routing?.mode,\n } = options;\n\n if (!locale) {\n return {\n prefix: '',\n localePrefix: undefined,\n };\n }\n\n // Handle prefix-based modes (prefix-all or prefix-no-default)\n const shouldPrefix =\n mode === 'prefix-all' ||\n (mode === 'prefix-no-default' && defaultLocale !== locale);\n\n if (shouldPrefix) {\n return {\n prefix: `${locale}/`,\n localePrefix: locale as Locale,\n };\n }\n\n return {\n prefix: '',\n localePrefix: undefined,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAa,aACX,QACA,UAGI,EAAE,KACc;CACpB,MAAM,EACJ,gBAAgB,eAAe,sBAAsB,eACrD,OAAO,eAAe,SAAS,SAC7B;AAEJ,KAAI,CAAC,OACH,QAAO;EACL,QAAQ;EACR,cAAc;EACf;AAQH,KAHE,SAAS,gBACR,SAAS,uBAAuB,kBAAkB,OAGnD,QAAO;EACL,QAAQ,GAAG,OAAO;EAClB,cAAc;EACf;AAGH,QAAO;EACL,QAAQ;EACR,cAAc;EACf"}
1
+ {"version":3,"file":"getPrefix.mjs","names":[],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport { DefaultValues } from '@intlayer/config/client';\nimport type { Locale, LocalesValues } from '@intlayer/types';\n\nexport type GetPrefixOptions = {\n defaultLocale?: LocalesValues;\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n};\n\nexport type GetPrefixResult = {\n /**\n * The complete base URL path with leading and trailing slashes.\n *\n * @example\n * // https://example.com/fr/about -> '/fr'\n * // https://example.com/about -> ''\n */\n prefix: string;\n /**\n * The locale identifier without slashes.\n *\n * @example\n * // https://example.com/fr/about -> 'fr'\n * // https://example.com/about -> undefined\n */\n localePrefix: Locale | undefined;\n};\n\n/**\n * Determines the URL prefix for a given locale based on the routing mode configuration.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode with default locale\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // prefix-no-default mode with non-default locale\n * getPrefix('fr', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '/fr', localePrefix: 'fr' }\n *\n * // prefix-all mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { prefix: '/en', localePrefix: locale }\n *\n * // search-params mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'search-params' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // no-prefix mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { prefix: '', localePrefix: undefined }\n * ```\n *\n * @param locale - The locale to check for prefix. If not provided, uses configured default locale.\n * @param options - Configuration options\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object containing pathPrefix, prefix, and localePrefix for the given locale.\n */\nexport const getPrefix = (\n locale: LocalesValues | undefined,\n options: {\n defaultLocale?: LocalesValues;\n locales?: LocalesValues[];\n mode?: 'prefix-no-default' | 'prefix-all' | 'no-prefix' | 'search-params';\n } = {}\n): GetPrefixResult => {\n const { defaultLocale, mode, locales } = {\n defaultLocale:\n configuration?.internationalization?.defaultLocale ??\n DefaultValues.Internationalization.DEFAULT_LOCALE,\n mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,\n locales:\n configuration?.internationalization?.locales ??\n DefaultValues.Internationalization.LOCALES,\n ...options,\n };\n\n if (!locale || !locales.includes(locale)) {\n return {\n prefix: '',\n localePrefix: undefined,\n };\n }\n\n // Handle prefix-based modes (prefix-all or prefix-no-default)\n const shouldPrefix =\n mode === 'prefix-all' ||\n (mode === 'prefix-no-default' && defaultLocale !== locale);\n\n if (shouldPrefix) {\n return {\n prefix: `${locale}/`,\n localePrefix: locale as Locale,\n };\n }\n\n return {\n prefix: '',\n localePrefix: undefined,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAa,aACX,QACA,UAII,EAAE,KACc;CACpB,MAAM,EAAE,eAAe,MAAM,YAAY;EACvC,eACE,eAAe,sBAAsB,iBACrC,cAAc,qBAAqB;EACrC,MAAM,eAAe,SAAS,QAAQ,cAAc,QAAQ;EAC5D,SACE,eAAe,sBAAsB,WACrC,cAAc,qBAAqB;EACrC,GAAG;EACJ;AAED,KAAI,CAAC,UAAU,CAAC,QAAQ,SAAS,OAAO,CACtC,QAAO;EACL,QAAQ;EACR,cAAc;EACf;AAQH,KAHE,SAAS,gBACR,SAAS,uBAAuB,kBAAkB,OAGnD,QAAO;EACL,QAAQ,GAAG,OAAO;EAClB,cAAc;EACf;AAGH,QAAO;EACL,QAAQ;EACR,cAAc;EACf"}
@@ -10,5 +10,6 @@ import { getPrefix } from "./getPrefix.mjs";
10
10
  import { getLocalizedUrl } from "./getLocalizedUrl.mjs";
11
11
  import { getMultilingualUrls } from "./getMultilingualUrls.mjs";
12
12
  import { localeFlatMap, localeMap, localeRecord } from "./localeMapper.mjs";
13
+ import { validatePrefix } from "./validatePrefix.mjs";
13
14
 
14
- export { getBrowserLocale, getHTMLTextDir, getLocaleFromPath, getLocaleLang, getLocaleName, getLocalizedUrl, getMultilingualUrls, getPathWithoutLocale, getPrefix, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, localeStorageOptions };
15
+ export { getBrowserLocale, getHTMLTextDir, getLocaleFromPath, getLocaleLang, getLocaleName, getLocalizedUrl, getMultilingualUrls, getPathWithoutLocale, getPrefix, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, localeStorageOptions, validatePrefix };
@@ -0,0 +1,52 @@
1
+ import { getPrefix } from "./getPrefix.mjs";
2
+ import { DefaultValues } from "@intlayer/config/client";
3
+ import configuration from "@intlayer/config/built";
4
+
5
+ //#region src/localization/validatePrefix.ts
6
+ /**
7
+ * Checks whether a given locale is valid based on the configured locales.
8
+ *
9
+ * @param locale - The locale value to validate. Can be `undefined` or `null`.
10
+ * @param options - Optional configuration to override default settings.
11
+ * @param options.locales - Array of valid locales. Defaults to the configured internationalization locales.
12
+ * @param options.defaultLocale - The default locale to use as fallback. Defaults to the configured default locale.
13
+ * @param options.mode - The routing mode (`'prefix'`, `'prefix-all'`, or `'no-prefix'`). Defaults to the configured routing mode.
14
+ * @returns An object containing the validation result and the locale prefix.
15
+ *
16
+ * @example
17
+ * // Check if 'en' is a valid locale
18
+ * const { isValid, localePrefix } = validatePrefix('en');
19
+ *
20
+ * @example
21
+ * // Check with custom options
22
+ * const { isValid, localePrefix } = validatePrefix('fr', {
23
+ * locales: ['en', 'fr', 'es'],
24
+ * defaultLocale: 'en',
25
+ * mode: 'prefix-all',
26
+ * });
27
+ */
28
+ const validatePrefix = (locale, options) => {
29
+ const { defaultLocale, mode, locales } = {
30
+ defaultLocale: configuration?.internationalization?.defaultLocale ?? DefaultValues.Internationalization.DEFAULT_LOCALE,
31
+ mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,
32
+ locales: configuration?.internationalization?.locales ?? DefaultValues.Internationalization.LOCALES,
33
+ ...options
34
+ };
35
+ const { localePrefix } = getPrefix(locale || defaultLocale, {
36
+ mode,
37
+ locales,
38
+ defaultLocale
39
+ });
40
+ if (localePrefix === locale && locale === void 0) return {
41
+ isValid: true,
42
+ localePrefix: void 0
43
+ };
44
+ return {
45
+ isValid: locales.some((localeEl) => localeEl === locale),
46
+ localePrefix
47
+ };
48
+ };
49
+
50
+ //#endregion
51
+ export { validatePrefix };
52
+ //# sourceMappingURL=validatePrefix.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validatePrefix.mjs","names":[],"sources":["../../../src/localization/validatePrefix.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport { DefaultValues } from '@intlayer/config/client';\nimport type { LocalesValues } from '@intlayer/types';\nimport { getPrefix } from './getPrefix';\n\nexport type ValidatePrefixResult = {\n isValid: boolean;\n localePrefix: string | undefined;\n};\n\n/**\n * Checks whether a given locale is valid based on the configured locales.\n *\n * @param locale - The locale value to validate. Can be `undefined` or `null`.\n * @param options - Optional configuration to override default settings.\n * @param options.locales - Array of valid locales. Defaults to the configured internationalization locales.\n * @param options.defaultLocale - The default locale to use as fallback. Defaults to the configured default locale.\n * @param options.mode - The routing mode (`'prefix'`, `'prefix-all'`, or `'no-prefix'`). Defaults to the configured routing mode.\n * @returns An object containing the validation result and the locale prefix.\n *\n * @example\n * // Check if 'en' is a valid locale\n * const { isValid, localePrefix } = validatePrefix('en');\n *\n * @example\n * // Check with custom options\n * const { isValid, localePrefix } = validatePrefix('fr', {\n * locales: ['en', 'fr', 'es'],\n * defaultLocale: 'en',\n * mode: 'prefix-all',\n * });\n */\nexport const validatePrefix = (\n locale: LocalesValues | undefined | null,\n options?: {\n locales?: LocalesValues[];\n defaultLocale?: LocalesValues;\n mode?: typeof configuration.routing.mode;\n }\n): ValidatePrefixResult => {\n const { defaultLocale, mode, locales } = {\n defaultLocale:\n configuration?.internationalization?.defaultLocale ??\n DefaultValues.Internationalization.DEFAULT_LOCALE,\n mode: configuration?.routing?.mode ?? DefaultValues.Routing.ROUTING_MODE,\n locales:\n configuration?.internationalization?.locales ??\n DefaultValues.Internationalization.LOCALES,\n ...options,\n };\n\n // If no locale provided (optional param), will use default\n // In `routing.mode = 'prefix-all'`, the locale is required to be a valid locale\n const { localePrefix } = getPrefix(locale || defaultLocale, {\n mode,\n locales,\n defaultLocale,\n });\n\n if (localePrefix === locale && locale === undefined) {\n return { isValid: true, localePrefix: undefined };\n }\n\n // Check if the provided locale is valid\n const isValid: boolean = locales.some((localeEl) => localeEl === locale);\n\n return { isValid: isValid, localePrefix };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,kBACX,QACA,YAKyB;CACzB,MAAM,EAAE,eAAe,MAAM,YAAY;EACvC,eACE,eAAe,sBAAsB,iBACrC,cAAc,qBAAqB;EACrC,MAAM,eAAe,SAAS,QAAQ,cAAc,QAAQ;EAC5D,SACE,eAAe,sBAAsB,WACrC,cAAc,qBAAqB;EACrC,GAAG;EACJ;CAID,MAAM,EAAE,iBAAiB,UAAU,UAAU,eAAe;EAC1D;EACA;EACA;EACD,CAAC;AAEF,KAAI,iBAAiB,UAAU,WAAW,OACxC,QAAO;EAAE,SAAS;EAAM,cAAc;EAAW;AAMnD,QAAO;EAAE,SAFgB,QAAQ,MAAM,aAAa,aAAa,OAAO;EAE7C;EAAc"}
@@ -0,0 +1,307 @@
1
+ import { deepTransformNode } from "../interpreter/getContent/deepTransform.mjs";
2
+ import { enu as enumeration } from "../transpiler/enumeration/enumeration.mjs";
3
+ import { gender } from "../transpiler/gender/gender.mjs";
4
+ import { insert as insertion } from "../transpiler/insertion/insertion.mjs";
5
+ import { NodeType } from "@intlayer/types";
6
+
7
+ //#region src/messageFormat/ICU.ts
8
+ const parseICU = (text) => {
9
+ let index = 0;
10
+ const parseNodes = () => {
11
+ const nodes = [];
12
+ let currentText = "";
13
+ while (index < text.length) {
14
+ const char = text[index];
15
+ if (char === "{") {
16
+ if (currentText) {
17
+ nodes.push(currentText);
18
+ currentText = "";
19
+ }
20
+ index++;
21
+ nodes.push(parseArgument());
22
+ } else if (char === "}") break;
23
+ else if (char === "'") if (index + 1 < text.length && text[index + 1] === "'") {
24
+ currentText += "'";
25
+ index += 2;
26
+ } else {
27
+ const nextQuote = text.indexOf("'", index + 1);
28
+ if (nextQuote !== -1) {
29
+ currentText += text.substring(index + 1, nextQuote);
30
+ index = nextQuote + 1;
31
+ } else {
32
+ currentText += "'";
33
+ index++;
34
+ }
35
+ }
36
+ else {
37
+ currentText += char;
38
+ index++;
39
+ }
40
+ }
41
+ if (currentText) nodes.push(currentText);
42
+ return nodes;
43
+ };
44
+ const parseArgument = () => {
45
+ let name = "";
46
+ while (index < text.length && /[^,}]/.test(text[index])) {
47
+ name += text[index];
48
+ index++;
49
+ }
50
+ name = name.trim();
51
+ if (index >= text.length) throw new Error("Unclosed argument");
52
+ if (text[index] === "}") {
53
+ index++;
54
+ return {
55
+ type: "argument",
56
+ name
57
+ };
58
+ }
59
+ if (text[index] === ",") {
60
+ index++;
61
+ let type = "";
62
+ while (index < text.length && /[^,}]/.test(text[index])) {
63
+ type += text[index];
64
+ index++;
65
+ }
66
+ type = type.trim();
67
+ if (index >= text.length) throw new Error("Unclosed argument");
68
+ if (text[index] === "}") {
69
+ index++;
70
+ return {
71
+ type: "argument",
72
+ name,
73
+ format: { type }
74
+ };
75
+ }
76
+ if (text[index] === ",") {
77
+ index++;
78
+ if (type === "plural" || type === "select") {
79
+ const options = {};
80
+ while (index < text.length && text[index] !== "}") {
81
+ while (index < text.length && /\s/.test(text[index])) index++;
82
+ let key = "";
83
+ while (index < text.length && /[^{\s]/.test(text[index])) {
84
+ key += text[index];
85
+ index++;
86
+ }
87
+ while (index < text.length && /\s/.test(text[index])) index++;
88
+ if (text[index] !== "{") throw new Error("Expected { after option key");
89
+ index++;
90
+ const value = parseNodes();
91
+ if (text[index] !== "}") throw new Error("Expected } after option value");
92
+ index++;
93
+ options[key] = value;
94
+ while (index < text.length && /\s/.test(text[index])) index++;
95
+ }
96
+ index++;
97
+ if (type === "plural") return {
98
+ type: "plural",
99
+ name,
100
+ options
101
+ };
102
+ else if (type === "select") return {
103
+ type: "select",
104
+ name,
105
+ options
106
+ };
107
+ } else {
108
+ let style = "";
109
+ while (index < text.length && text[index] !== "}") {
110
+ style += text[index];
111
+ index++;
112
+ }
113
+ if (index >= text.length) throw new Error("Unclosed argument");
114
+ style = style.trim();
115
+ index++;
116
+ return {
117
+ type: "argument",
118
+ name,
119
+ format: {
120
+ type,
121
+ style
122
+ }
123
+ };
124
+ }
125
+ }
126
+ }
127
+ throw new Error("Malformed argument");
128
+ };
129
+ return parseNodes();
130
+ };
131
+ const icuNodesToIntlayer = (nodes) => {
132
+ if (nodes.length === 0) return "";
133
+ if (nodes.length === 1 && typeof nodes[0] === "string") return nodes[0];
134
+ if (nodes.every((node) => typeof node === "string" || node.type === "argument")) {
135
+ let str = "";
136
+ for (const node of nodes) if (typeof node === "string") str += node;
137
+ else if (typeof node !== "string" && node.type === "argument") if (node.format) str += `{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`;
138
+ else str += `{{${node.name}}}`;
139
+ return insertion(str);
140
+ }
141
+ if (nodes.length === 1) {
142
+ const node = nodes[0];
143
+ if (typeof node === "string") return node;
144
+ if (node.type === "argument") {
145
+ if (node.format) return insertion(`{${node.name}, ${node.format.type}${node.format.style ? `, ${node.format.style}` : ""}}`);
146
+ return insertion(`{{${node.name}}}`);
147
+ }
148
+ if (node.type === "plural") {
149
+ const options = {};
150
+ for (const [key, val] of Object.entries(node.options)) {
151
+ let newKey = key;
152
+ if (key.startsWith("=")) newKey = key.substring(1);
153
+ else if (key === "one") newKey = "1";
154
+ else if (key === "two") newKey = "2";
155
+ else if (key === "few") newKey = "<=3";
156
+ else if (key === "many") newKey = ">=4";
157
+ else if (key === "other") newKey = "fallback";
158
+ options[newKey] = icuNodesToIntlayer(val.map((v) => {
159
+ if (typeof v === "string") return v.replace(/#/g, `{{${node.name}}}`);
160
+ return v;
161
+ }));
162
+ }
163
+ options.__intlayer_icu_var = node.name;
164
+ return enumeration(options);
165
+ }
166
+ if (node.type === "select") {
167
+ const options = {};
168
+ for (const [key, val] of Object.entries(node.options)) options[key] = icuNodesToIntlayer(val);
169
+ const optionKeys = Object.keys(options);
170
+ if ((options.male || options.female) && optionKeys.every((k) => [
171
+ "male",
172
+ "female",
173
+ "other",
174
+ "fallback"
175
+ ].includes(k))) return gender({
176
+ fallback: options.other,
177
+ male: options.male,
178
+ female: options.female
179
+ });
180
+ options.__intlayer_icu_var = node.name;
181
+ return enumeration(options);
182
+ }
183
+ }
184
+ return nodes.map((node) => icuNodesToIntlayer([node]));
185
+ };
186
+ const icuToIntlayerPlugin = {
187
+ canHandle: (node) => typeof node === "string" && (node.includes("{") || node.includes("}")),
188
+ transform: (node) => {
189
+ try {
190
+ return icuNodesToIntlayer(parseICU(node));
191
+ } catch {
192
+ return node;
193
+ }
194
+ }
195
+ };
196
+ const intlayerToIcuPlugin = {
197
+ canHandle: (node) => typeof node === "string" && (node.includes("{") || node.includes("}")) || node && typeof node === "object" && (node.nodeType === NodeType.Insertion || node.nodeType === NodeType.Enumeration || node.nodeType === NodeType.Gender || node.nodeType === "composite") || Array.isArray(node),
198
+ transform: (node, props, next) => {
199
+ if (typeof node === "string") return node.replace(/\{\{([^}]+)\}\}/g, "{$1}");
200
+ if (node.nodeType === NodeType.Insertion) return node.insertion.replace(/\{\{([^}]+)\}\}/g, "{$1}");
201
+ if (node.nodeType === NodeType.Enumeration) {
202
+ const options = node.enumeration;
203
+ const transformedOptions = {};
204
+ for (const [key, val] of Object.entries(options)) {
205
+ if (key === "__intlayer_icu_var") continue;
206
+ const childVal = next(val, props);
207
+ transformedOptions[key] = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
208
+ }
209
+ let varName = options.__intlayer_icu_var || "n";
210
+ if (!options.__intlayer_icu_var) {
211
+ const match = (transformedOptions.fallback || transformedOptions.other || Object.values(transformedOptions)[0]).match(/\{([a-zA-Z0-9_]+)\}(?!,)/);
212
+ if (match) varName = match[1];
213
+ }
214
+ const keys = Object.keys(transformedOptions);
215
+ const pluralKeys = [
216
+ "1",
217
+ "2",
218
+ "<=3",
219
+ ">=4",
220
+ "fallback",
221
+ "other",
222
+ "zero",
223
+ "one",
224
+ "two",
225
+ "few",
226
+ "many"
227
+ ];
228
+ const isPlural = keys.every((k) => pluralKeys.includes(k) || /^[<>=]?\d+(\.\d+)?$/.test(k));
229
+ const parts = [];
230
+ if (isPlural) {
231
+ for (const [key, val] of Object.entries(transformedOptions)) {
232
+ let icuKey = key;
233
+ if (key === "fallback") icuKey = "other";
234
+ else if (key === "<=3") icuKey = "few";
235
+ else if (key === ">=4") icuKey = "many";
236
+ else if (/^\d+$/.test(key)) icuKey = `=${key}`;
237
+ else if ([
238
+ "zero",
239
+ "few",
240
+ "many"
241
+ ].includes(key)) icuKey = key;
242
+ else icuKey = "other";
243
+ let strVal = val;
244
+ strVal = strVal.replace(new RegExp(`\\{${varName}\\}`, "g"), "#");
245
+ parts.push(`${icuKey} {${strVal}}`);
246
+ }
247
+ return `{${varName}, plural, ${parts.join(" ")}}`;
248
+ } else {
249
+ const entries = Object.entries(transformedOptions).sort(([keyA], [keyB]) => {
250
+ if (keyA === "fallback" || keyA === "other") return 1;
251
+ if (keyB === "fallback" || keyB === "other") return -1;
252
+ return 0;
253
+ });
254
+ for (const [key, val] of entries) {
255
+ let icuKey = key;
256
+ if (key === "fallback") icuKey = "other";
257
+ parts.push(`${icuKey} {${val}}`);
258
+ }
259
+ return `{${varName}, select, ${parts.join(" ")}}`;
260
+ }
261
+ }
262
+ if (node.nodeType === NodeType.Gender) {
263
+ const options = node.gender;
264
+ const varName = "gender";
265
+ const parts = [];
266
+ const entries = Object.entries(options).sort(([keyA], [keyB]) => {
267
+ if (keyA === "fallback") return 1;
268
+ if (keyB === "fallback") return -1;
269
+ return 0;
270
+ });
271
+ for (const [key, val] of entries) {
272
+ let icuKey = key;
273
+ if (key === "fallback") icuKey = "other";
274
+ const childVal = next(val, props);
275
+ const strVal = typeof childVal === "string" ? childVal : JSON.stringify(childVal);
276
+ parts.push(`${icuKey} {${strVal}}`);
277
+ }
278
+ return `{${varName}, select, ${parts.join(" ")}}`;
279
+ }
280
+ if (Array.isArray(node) || node.nodeType === "composite" && Array.isArray(node.composite)) return (Array.isArray(node) ? node : node.composite).map((item) => next(item, props)).join("");
281
+ return next(node, props);
282
+ }
283
+ };
284
+ const intlayerToICUFormatter = (message) => {
285
+ return deepTransformNode(message, {
286
+ dictionaryKey: "icu",
287
+ keyPath: [],
288
+ plugins: [{
289
+ id: "icu",
290
+ ...intlayerToIcuPlugin
291
+ }]
292
+ });
293
+ };
294
+ const icuToIntlayerFormatter = (message) => {
295
+ return deepTransformNode(message, {
296
+ dictionaryKey: "icu",
297
+ keyPath: [],
298
+ plugins: [{
299
+ id: "icu",
300
+ ...icuToIntlayerPlugin
301
+ }]
302
+ });
303
+ };
304
+
305
+ //#endregion
306
+ export { icuToIntlayerFormatter, intlayerToICUFormatter };
307
+ //# sourceMappingURL=ICU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ICU.mjs","names":["nodes: ICUNode[]","options: Record<string, ICUNode[]>","insert","options: Record<string, any>","enu","transformedOptions: Record<string, string>"],"sources":["../../../src/messageFormat/ICU.ts"],"sourcesContent":["import { type Dictionary, NodeType } from '@intlayer/types';\nimport { deepTransformNode } from '../interpreter';\nimport { enu, gender, insert } from '../transpiler';\n\n// Types for our AST\ntype ICUNode =\n | string\n | {\n type: 'argument';\n name: string;\n format?: { type: string; style?: string };\n }\n | { type: 'plural'; name: string; options: Record<string, ICUNode[]> }\n | { type: 'select'; name: string; options: Record<string, ICUNode[]> };\n\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nconst parseICU = (text: string): ICUNode[] => {\n let index = 0;\n\n const parseNodes = (): ICUNode[] => {\n const nodes: ICUNode[] = [];\n let currentText = '';\n\n while (index < text.length) {\n const char = text[index];\n\n if (char === '{') {\n if (currentText) {\n nodes.push(currentText);\n currentText = '';\n }\n index++; // skip {\n nodes.push(parseArgument());\n } else if (char === '}') {\n // End of current block\n break;\n } else if (char === \"'\") {\n // Escaping\n if (index + 1 < text.length && text[index + 1] === \"'\") {\n currentText += \"'\";\n index += 2;\n } else {\n // Find next quote\n const nextQuote = text.indexOf(\"'\", index + 1);\n if (nextQuote !== -1) {\n // Determine if this is escaping syntax characters\n // For simplicity, we'll treat content between single quotes as literal\n // provided it contains syntax chars.\n // Standard ICU: ' quoted string '\n // If it is just an apostrophe, it should be doubled.\n // But simplified: take content between quotes literally.\n currentText += text.substring(index + 1, nextQuote);\n index = nextQuote + 1;\n } else {\n currentText += \"'\";\n index++;\n }\n }\n } else {\n currentText += char;\n index++;\n }\n }\n\n if (currentText) {\n nodes.push(currentText);\n }\n return nodes;\n };\n\n const parseArgument = (): ICUNode => {\n // We are past '{'\n // Parse name\n let name = '';\n while (index < text.length && /[^,}]/.test(text[index])) {\n name += text[index];\n index++;\n }\n name = name.trim();\n\n if (index >= text.length) throw new Error('Unclosed argument');\n\n if (text[index] === '}') {\n index++;\n return { type: 'argument', name };\n }\n\n // Must be comma\n if (text[index] === ',') {\n index++;\n // Parse type\n let type = '';\n while (index < text.length && /[^,}]/.test(text[index])) {\n type += text[index];\n index++;\n }\n type = type.trim();\n\n if (index >= text.length) throw new Error('Unclosed argument');\n\n if (text[index] === '}') {\n index++;\n return { type: 'argument', name, format: { type } };\n }\n\n if (text[index] === ',') {\n index++;\n\n // If plural or select, parse options\n if (type === 'plural' || type === 'select') {\n // Parse options\n const options: Record<string, ICUNode[]> = {};\n\n while (index < text.length && text[index] !== '}') {\n // skip whitespace\n while (index < text.length && /\\s/.test(text[index])) index++;\n\n // parse key\n let key = '';\n while (index < text.length && /[^{\\s]/.test(text[index])) {\n key += text[index];\n index++;\n }\n\n while (index < text.length && /\\s/.test(text[index])) index++;\n\n if (text[index] !== '{')\n throw new Error('Expected { after option key');\n index++; // skip {\n\n const value = parseNodes();\n\n if (text[index] !== '}')\n throw new Error('Expected } after option value');\n index++; // skip }\n\n options[key] = value;\n\n while (index < text.length && /\\s/.test(text[index])) index++;\n }\n\n index++; // skip closing argument }\n\n if (type === 'plural') {\n return { type: 'plural', name, options };\n } else if (type === 'select') {\n return { type: 'select', name, options };\n }\n } else {\n // Parse style for number/date/time\n let style = '';\n while (index < text.length && text[index] !== '}') {\n style += text[index];\n index++;\n }\n if (index >= text.length) throw new Error('Unclosed argument');\n\n style = style.trim();\n index++; // skip }\n\n return { type: 'argument', name, format: { type, style } };\n }\n }\n }\n\n throw new Error('Malformed argument');\n };\n\n return parseNodes();\n};\n\nconst icuNodesToIntlayer = (nodes: ICUNode[]): any => {\n if (nodes.length === 0) return '';\n if (nodes.length === 1 && typeof nodes[0] === 'string') return nodes[0];\n\n // Check if we can flatten to a single string (insert)\n const canFlatten = nodes.every(\n (node) => typeof node === 'string' || node.type === 'argument'\n );\n if (canFlatten) {\n let str = '';\n for (const node of nodes) {\n if (typeof node === 'string') {\n str += node;\n } else if (typeof node !== 'string' && node.type === 'argument') {\n if (node.format) {\n str += `{${node.name}, ${node.format.type}${\n node.format.style ? `, ${node.format.style}` : ''\n }}`;\n } else {\n str += `{{${node.name}}}`;\n }\n }\n }\n return insert(str);\n }\n\n // Mix of string and complex types.\n // If we have just one complex type and it covers everything?\n if (nodes.length === 1) {\n const node = nodes[0];\n\n if (typeof node === 'string') return node; // already handled\n if (node.type === 'argument') {\n if (node.format) {\n return insert(\n `{${node.name}, ${node.format.type}${\n node.format.style ? `, ${node.format.style}` : ''\n }}`\n );\n }\n return insert(`{{${node.name}}}`);\n }\n if (node.type === 'plural') {\n const options: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(node.options)) {\n // Map ICU keys to Intlayer keys\n let newKey = key;\n if (key.startsWith('=')) {\n newKey = key.substring(1); // =0 -> 0\n } else if (key === 'one') {\n newKey = '1';\n } else if (key === 'two') {\n newKey = '2';\n } else if (key === 'few') {\n newKey = '<=3';\n } else if (key === 'many') {\n newKey = '>=4';\n } else if (key === 'other') {\n newKey = 'fallback';\n }\n // Handle # in plural value\n // For plural, we need to pass the variable name down or replace #\n // Intlayer uses {{n}} (or whatever var name)\n // We should replace # with {{n}} in the string parts of val\n const replacedVal = val.map((v) => {\n if (typeof v === 'string') {\n return v.replace(/#/g, `{{${node.name}}}`);\n }\n return v;\n });\n\n options[newKey] = icuNodesToIntlayer(replacedVal);\n }\n\n // Preserve variable name\n options.__intlayer_icu_var = node.name;\n\n return enu(options);\n }\n if (node.type === 'select') {\n const options: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(node.options)) {\n options[key] = icuNodesToIntlayer(val);\n }\n\n // Check if it looks like gender\n const optionKeys = Object.keys(options);\n // It is gender if it has 'male' OR 'female' AND only contains gender keys (male, female, other)\n const isGender =\n (options.male || options.female) &&\n optionKeys.every((k) =>\n ['male', 'female', 'other', 'fallback'].includes(k)\n );\n\n if (isGender) {\n return gender({\n fallback: options.other,\n male: options.male,\n female: options.female,\n });\n }\n\n // Preserve variable name\n options.__intlayer_icu_var = node.name;\n\n return enu(options);\n }\n }\n\n // If multiple nodes, return array\n return nodes.map((node) => icuNodesToIntlayer([node]));\n};\n\nconst icuToIntlayerPlugin = {\n canHandle: (node: any) =>\n typeof node === 'string' && (node.includes('{') || node.includes('}')),\n transform: (node: any) => {\n try {\n const ast = parseICU(node);\n return icuNodesToIntlayer(ast);\n } catch {\n // If parsing fails, return original string\n return node;\n }\n },\n};\n\nconst intlayerToIcuPlugin = {\n canHandle: (node: any) =>\n (typeof node === 'string' && (node.includes('{') || node.includes('}'))) ||\n (node &&\n typeof node === 'object' &&\n (node.nodeType === NodeType.Insertion ||\n node.nodeType === NodeType.Enumeration ||\n node.nodeType === NodeType.Gender ||\n node.nodeType === 'composite')) ||\n Array.isArray(node),\n transform: (node: any, props: any, next: any) => {\n if (typeof node === 'string') {\n return node.replace(/\\{\\{([^}]+)\\}\\}/g, '{$1}');\n }\n\n if (node.nodeType === NodeType.Insertion) {\n // insert(\"Hello {{name}}\") -> \"Hello {name}\"\n return node.insertion.replace(/\\{\\{([^}]+)\\}\\}/g, '{$1}');\n }\n\n if (node.nodeType === NodeType.Enumeration) {\n const options = node.enumeration;\n\n // Transform all values first\n const transformedOptions: Record<string, string> = {};\n for (const [key, val] of Object.entries(options)) {\n if (key === '__intlayer_icu_var') continue;\n const childVal = next(val, props);\n transformedOptions[key] =\n typeof childVal === 'string' ? childVal : JSON.stringify(childVal);\n }\n\n // Infer variable name\n let varName = options.__intlayer_icu_var || 'n';\n\n if (!options.__intlayer_icu_var) {\n const fallbackVal =\n transformedOptions.fallback ||\n transformedOptions.other ||\n Object.values(transformedOptions)[0];\n // Match {variable} but avoid {variable, ...} (which are nested ICUs)\n // Actually nested ICU starts with {var, ...\n // Simple variable is {var}\n // We look for {var} that is NOT followed by ,\n const match = fallbackVal.match(/\\{([a-zA-Z0-9_]+)\\}(?!,)/);\n if (match) {\n varName = match[1];\n }\n }\n\n const keys = Object.keys(transformedOptions);\n const pluralKeys = [\n '1',\n '2',\n '<=3',\n '>=4',\n 'fallback',\n 'other',\n 'zero',\n 'one',\n 'two',\n 'few',\n 'many',\n ];\n // Also check for numbers\n const isPlural = keys.every(\n (k) => pluralKeys.includes(k) || /^[<>=]?\\d+(\\.\\d+)?$/.test(k)\n );\n\n const parts = [];\n\n if (isPlural) {\n for (const [key, val] of Object.entries(transformedOptions)) {\n let icuKey = key;\n\n if (key === 'fallback') icuKey = 'other';\n else if (key === '<=3') icuKey = 'few';\n else if (key === '>=4') icuKey = 'many';\n else if (/^\\d+$/.test(key)) icuKey = `=${key}`;\n else if (['zero', 'few', 'many'].includes(key)) icuKey = key;\n else icuKey = 'other';\n\n let strVal = val;\n\n // Replace {varName} with #\n // Note: next() has already converted {{var}} -> {var}\n strVal = strVal.replace(new RegExp(`\\\\{${varName}\\\\}`, 'g'), '#');\n\n parts.push(`${icuKey} {${strVal}}`);\n }\n\n return `{${varName}, plural, ${parts.join(' ')}}`;\n } else {\n // Select\n const entries = Object.entries(transformedOptions).sort(\n ([keyA], [keyB]) => {\n if (keyA === 'fallback' || keyA === 'other') return 1;\n if (keyB === 'fallback' || keyB === 'other') return -1;\n return 0;\n }\n );\n\n for (const [key, val] of entries) {\n let icuKey = key;\n if (key === 'fallback') icuKey = 'other';\n // Do NOT map other keys to 'other'. Keep 'active', 'inactive', etc.\n\n parts.push(`${icuKey} {${val}}`);\n }\n return `{${varName}, select, ${parts.join(' ')}}`;\n }\n }\n\n if (node.nodeType === NodeType.Gender) {\n const options = node.gender;\n const varName = 'gender';\n const parts = [];\n\n const entries = Object.entries(options).sort(([keyA], [keyB]) => {\n if (keyA === 'fallback') return 1;\n if (keyB === 'fallback') return -1;\n return 0;\n });\n\n for (const [key, val] of entries) {\n let icuKey = key;\n if (key === 'fallback') icuKey = 'other';\n\n const childVal = next(val, props);\n const strVal =\n typeof childVal === 'string' ? childVal : JSON.stringify(childVal);\n\n parts.push(`${icuKey} {${strVal}}`);\n }\n return `{${varName}, select, ${parts.join(' ')}}`;\n }\n\n if (\n Array.isArray(node) ||\n (node.nodeType === 'composite' && Array.isArray(node.composite))\n ) {\n // handle array/composite\n const arr = Array.isArray(node) ? node : node.composite;\n const items = arr.map((item: any) => next(item, props));\n return items.join('');\n }\n\n return next(node, props);\n },\n};\n\nexport const intlayerToICUFormatter = (\n message: Dictionary['content']\n): JsonValue => {\n return deepTransformNode(message, {\n dictionaryKey: 'icu',\n keyPath: [],\n plugins: [{ id: 'icu', ...intlayerToIcuPlugin }],\n });\n};\n\nexport const icuToIntlayerFormatter = (\n message: Dictionary['content']\n): JsonValue => {\n return deepTransformNode(message, {\n dictionaryKey: 'icu',\n keyPath: [],\n plugins: [{ id: 'icu', ...icuToIntlayerPlugin }],\n });\n};\n"],"mappings":"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"}