@open-xchange/rolldown-plugin-i18next-gettext 1.0.1 → 1.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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## `1.1.0` – 2026-Apr-15
4
+
5
+ - removed: direct imports of PO files
6
+ - added: allow to group PO files into different namespaces
7
+
3
8
  ## `1.0.1` – 2026-Apr-14
4
9
 
5
10
  - fixed: removed debug output
package/README.md CHANGED
@@ -1,12 +1,11 @@
1
1
  # @open-xchange/rolldown-plugin-i18next-gettext
2
2
 
3
- A [rolldown](https://rolldown.rs/) plugin that allows building libraries using [i18next](https://www.i18next.com/) in source code with gettext's `.po` and `.pot` files under the hood.
3
+ A [rolldown](https://rolldown.rs/) plugin that allows building libraries or applications using [i18next](https://www.i18next.com/) in source code with gettext's `.po` and `.pot` files under the hood.
4
4
 
5
5
  This plugin has the following responsibilities:
6
6
 
7
- - All `.po` files imported in source code will be converted to i18next translation catalogs.
8
- - A virtual module import makes all translation catalogs from selected `.po` files available in a single dictionary to be able to pick translation languages dynamically.
9
- - The source code will be scanned for translation strings (`t` function calls), and a `.pot` file containing all strings will be generated into the output directory.
7
+ - Virtual module imports provide translation catalogs from selected `.po` files in a single namespace dictionary.
8
+ - The source code will be scanned for translation strings (`t` function calls), and `.pot` files containing all strings will be generated into the output directory. Supports i18next namespaces (one `.pot` file per namespace).
10
9
 
11
10
  ## Configuration
12
11
 
@@ -20,12 +19,13 @@ import i18nextPlugin from '@open-xchange/rolldown-plugin-i18next-gettext'
20
19
  export default defineConfig(() => {
21
20
  plugins: [
22
21
  i18nextPlugin({
23
- poFiles: 'i18n/*.po',
24
- potFiles: {
25
- srcFiles: 'src/**/*.{js,jsx,ts,tsx}',
26
- outputPath: '[NAMESPACE].pot',
27
- projectName: 'My Project',
22
+ poFiles: {
23
+ default: 'i18n/*.po',
24
+ custom: 'i18n/custom/*.po',
28
25
  },
26
+ srcFiles: 'src/**/*.{js,jsx,ts,tsx}',
27
+ potFiles: '[NAMESPACE].pot',
28
+ projectName: 'My Project',
29
29
  }),
30
30
  ],
31
31
  })
@@ -33,17 +33,22 @@ export default defineConfig(() => {
33
33
 
34
34
  ### Options
35
35
 
36
+ Type `Globs` can be a single glob pattern as string, or an array of glob patterns.
37
+
38
+ ```ts
39
+ type Globs = string | readonly string[]
40
+ ```
41
+
36
42
  | Name | Type | Default | Description |
37
43
  | - | - | - | - |
38
- | `poFiles` | `string\|string[]` | _required_ | Glob pattern(s) for all PO files containing the translations. |
39
- | `potFiles` | | | Configuration for the source code scanner and POT file generator. If omitted, no POT files will be generated. |
40
- | `potFiles.srcFiles` | `string\|string[]` | `src/**/*.{js,jsx,ts,tsx}` | Glob pattern(s) for all source files to be scanned for UI strings. |
41
- | `potFiles.outputPath` | `string` | `[NAMESPACE].pot` | Path to and filename of the generated POT files, relative to the build output directory. Should contain the placeholder `[NAMESPACE]` if the project contains translation strings in different i18next namespaces. |
42
- | `potFiles.projectName` | `string` | _required_ | The project name to be inserted into the POT file under the key 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]` |
44
+ | `poFiles` | `Record<string,Globs>` | _required_ | Glob pattern(s) for all PO files to be converted to i18next translation catalogs, mapped by i18next namespace identifiers. |
45
+ | `srcFiles` | `Globs` | `src/**/*.{js,jsx,ts,tsx}` | Glob pattern(s) for all source files to be scanned for UI strings for the POT file generator. |
46
+ | `potFiles` | `string` | `[NAMESPACE].pot` | Path to and filename of the generated POT files, relative to the build output directory. Must contain the placeholder `[NAMESPACE]` if the project contains translation strings in different i18next namespaces. |
47
+ | `projectName` | `string` | _required_ | The project name to be inserted into the POT file under the key 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]` |
43
48
 
44
49
  ## Usage
45
50
 
46
- Register TypeScript type definitions for the `virtual:i18next-po-catalogs` module import:
51
+ Register TypeScript type definitions for the virtual module imports:
47
52
 
48
53
  ```json
49
54
  // tsconfig.json
@@ -54,14 +59,14 @@ Register TypeScript type definitions for the `virtual:i18next-po-catalogs` modul
54
59
  }
55
60
  ```
56
61
 
57
- Use the virtual modules in your code:
62
+ Import translation catalogs in source code:
58
63
 
59
64
  ```ts
60
- // import catalogs for all languages
61
- import catalogs from 'virtual:i18next-po-catalogs'
62
- console.log(catalogs.de_DE)
65
+ // use namespace specifiers as configured in plugin's option `poFiles`
66
+ import translations from 'virtual:i18next-namespace:default'
63
67
 
64
- // import catalog for one language
65
- import catalog from 'i18n/de_DE.po'
66
- console.log(catalog)
68
+ i18next.init({ resources: {} });
69
+ for (const [lang, catalog] of Object.entries(translations)) {
70
+ i18next.addResources(lang, 'translation', catalog)
71
+ }
67
72
  ```
package/dist/index.d.mts CHANGED
@@ -1,55 +1,43 @@
1
1
  import { Plugin } from "rolldown";
2
2
 
3
3
  //#region src/index.d.ts
4
+ type Globs = string | readonly string[];
4
5
  /**
5
6
  * Configuration options for the plugin '@open-xchange/rolldown-plugin-i18next-gettext'.
6
7
  */
7
- interface RolldownPluginI18nextGettextOptions {
8
+ interface PluginI18nextGettextOptions {
8
9
  /**
9
- * Glob pattern(s) for all PO files containing the translations. Matching PO
10
- * files will be converted to i18next translation catalogs when imported in
11
- * source code directly, and will be included in the virtual module providing
12
- * a dictionary of all translation catalogs, mapped by the base names of the
13
- * PO files.
10
+ * Glob pattern(s) for all PO files to be converted to i18next translation
11
+ * catalogs, mapped by i18next namespace identifiers.
14
12
  */
15
- poFiles: string | readonly string[];
13
+ poFiles: Record<string, Globs>;
16
14
  /**
17
- * Configuration for the source code scanner and POT file generator. If
18
- * omitted, no POT files will be generated.
15
+ * Glob pattern(s) for all source files to be scanned for UI strings for the
16
+ * POT file generator.
17
+ *
18
+ * @default
19
+ * 'src/**\/*.{js,jsx,ts,tsx}'
19
20
  */
20
- potFiles?: {
21
- /**
22
- * Glob pattern(s) for all source files to be scanned for UI strings.
23
- *
24
- * @default
25
- * 'src/**\/*.{js,jsx,ts,tsx}'
26
- */
27
- srcFiles?: string | readonly string[];
28
- /**
29
- * Path to and filename of the generated POT files, relative to the build
30
- * output directory. Should contain the placeholder `[NAMESPACE]` if the
31
- * project contains translation strings in different i18next namespaces.
32
- *
33
- * @default
34
- * '[NAMESPACE].pot'
35
- */
36
- outputPath?: string;
37
- /**
38
- * The project name to be inserted into the POT files under the key
39
- * 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]`.
40
- */
41
- projectName: string;
42
- };
21
+ srcFiles?: Globs;
22
+ /**
23
+ * Path to and filename of the generated POT files, relative to the build
24
+ * output directory. Must contain the placeholder `[NAMESPACE]` if the
25
+ * project contains translation strings in different i18next namespaces.
26
+ *
27
+ * @default
28
+ * '[NAMESPACE].pot'
29
+ */
30
+ potFiles?: string;
31
+ /**
32
+ * The project name to be inserted into the POT files under the key
33
+ * 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]`.
34
+ */
35
+ projectName: string;
43
36
  }
44
37
  /**
45
38
  * Identifier of this plugin.
46
39
  */
47
40
  declare const PROJECT_NAME = "@open-xchange/rolldown-plugin-i18next-gettext";
48
- /**
49
- * Identifier of the virtual module providing all translation catalogs in a
50
- * single object.
51
- */
52
- declare const VIRTUAL_MODULE_ID = "virtual:i18next-po-catalogs";
53
41
  /**
54
42
  * Rolldown plugin for using i18next with gettext `.po` files under the hood.
55
43
  *
@@ -59,7 +47,7 @@ declare const VIRTUAL_MODULE_ID = "virtual:i18next-po-catalogs";
59
47
  * @returns
60
48
  * The rolldown plugin object.
61
49
  */
62
- declare function rolldownPluginI18nextGettext(options: RolldownPluginI18nextGettextOptions): Plugin;
50
+ declare function pluginI18nextGettext(options: PluginI18nextGettextOptions): Plugin;
63
51
  //#endregion
64
- export { PROJECT_NAME, RolldownPluginI18nextGettextOptions, VIRTUAL_MODULE_ID, rolldownPluginI18nextGettext as default };
52
+ export { Globs, PROJECT_NAME, PluginI18nextGettextOptions, pluginI18nextGettext as default };
65
53
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AAaA;UAAiB,mCAAA;;;;;;;;EASf,OAAA;EA8Ba;AASf;;;EAjCE,QAAA;IAiCuB;AAMzB;;;;;IA/BI,QAAA;IA4CgD;;;;;;;;IAlChD,UAAA;;;;;IAMA,WAAA;EAAA;AAAA;;;;cASS,YAAA;;;;;cAMA,iBAAA;;;;;;;;;;iBAaW,4BAAA,CAA6B,OAAA,EAAS,mCAAA,GAAsC,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAYY,KAAA;;AAAZ;;UAKiB,2BAAA;EALA;;AAKjB;;EAME,OAAA,EAAS,MAAA,SAAe,KAAA;EAAA;;;;;;;EASxB,QAAA,GAAW,KAAA;EAAX;;;;;;AAwBF;;EAdE,QAAA;EAcuB;;AAAkD;;EARzE,WAAA;AAAA;;;;cAQW,YAAA;;;;;;;;;;iBAaW,oBAAA,CAAqB,OAAA,EAAS,2BAAA,GAA8B,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as path from "node:path";
2
- import { createJsonModule, exactRegex, wrapArray } from "@open-xchange/rolldown-utils";
2
+ import { createJsonModule, getRemainder, prefixRegex } from "@open-xchange/rolldown-utils";
3
3
  import { glob } from "tinyglobby";
4
4
  import { parsePoFile, parseSourceFiles } from "@open-xchange/i18next-po-parser";
5
5
  //#region src/index.ts
@@ -8,11 +8,6 @@ import { parsePoFile, parseSourceFiles } from "@open-xchange/i18next-po-parser";
8
8
  */
9
9
  const PROJECT_NAME = "@open-xchange/rolldown-plugin-i18next-gettext";
10
10
  /**
11
- * Identifier of the virtual module providing all translation catalogs in a
12
- * single object.
13
- */
14
- const VIRTUAL_MODULE_ID = "virtual:i18next-po-catalogs";
15
- /**
16
11
  * Rolldown plugin for using i18next with gettext `.po` files under the hood.
17
12
  *
18
13
  * @param options
@@ -21,38 +16,39 @@ const VIRTUAL_MODULE_ID = "virtual:i18next-po-catalogs";
21
16
  * @returns
22
17
  * The rolldown plugin object.
23
18
  */
24
- function rolldownPluginI18nextGettext(options) {
25
- const RESOLVED_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
19
+ function pluginI18nextGettext(options) {
20
+ const VIRTUAL_MODULE_PREFIX = "virtual:i18next-namespace:";
26
21
  return {
27
22
  name: PROJECT_NAME,
28
23
  resolveId: {
29
- filter: { id: exactRegex(VIRTUAL_MODULE_ID) },
30
- handler: () => RESOLVED_MODULE_ID
24
+ filter: { id: prefixRegex(VIRTUAL_MODULE_PREFIX) },
25
+ handler: (id) => `\0${id}`
31
26
  },
32
27
  load: {
33
- filter: { id: [exactRegex(RESOLVED_MODULE_ID), ...wrapArray(options.poFiles)] },
28
+ filter: { id: prefixRegex(`\0${VIRTUAL_MODULE_PREFIX}`) },
34
29
  async handler(id) {
35
- if (id === RESOLVED_MODULE_ID) {
36
- const map = {};
37
- for (const entry of await glob(options.poFiles)) {
38
- const basename = path.parse(entry).name;
39
- map[basename] = await parsePoFile(entry);
40
- }
41
- return createJsonModule(map);
42
- }
43
- return createJsonModule(await parsePoFile(id));
30
+ const key = getRemainder(id, `\0${VIRTUAL_MODULE_PREFIX}`);
31
+ if (!key) this.error("missing namespace specifier");
32
+ const globs = options.poFiles[key];
33
+ if (!globs) this.error(`invalid namespace specifier: ${key}`);
34
+ const catalogs = Object.create(null);
35
+ const filePaths = await glob(globs);
36
+ await Promise.all(filePaths.map(async (filePath) => {
37
+ const basename = path.parse(filePath).name.replaceAll("_", "-");
38
+ catalogs[basename] = await parsePoFile(filePath);
39
+ }));
40
+ return createJsonModule(catalogs);
44
41
  }
45
42
  },
46
43
  async generateBundle() {
47
- if (!options.potFiles) return;
48
- const { srcFiles = "src/**/*.{js,jsx,ts,tsx}", outputPath = "[NAMESPACE].pot", projectName } = options.potFiles;
44
+ const { srcFiles = "src/**/*.{js,jsx,ts,tsx}", potFiles = "[NAMESPACE].pot", projectName } = options;
49
45
  const catalogs = await parseSourceFiles({
50
46
  files: srcFiles,
51
47
  project: projectName
52
48
  });
53
- if (catalogs.size > 1 && !outputPath.includes("[NAMESPACE]")) this.error("multiple POT files written to same file location (missing placeholder `[NAMESPACE]` in option potFile)");
49
+ if (catalogs.size > 1 && !potFiles.includes("[NAMESPACE]")) this.error("multiple POT files written to same file location (missing placeholder `[NAMESPACE]` in option potFile)");
54
50
  for (const [namespace, source] of catalogs) {
55
- const fileName = outputPath.replaceAll("[NAMESPACE]", namespace);
51
+ const fileName = potFiles.replaceAll("[NAMESPACE]", namespace);
56
52
  this.emitFile({
57
53
  type: "asset",
58
54
  fileName,
@@ -63,6 +59,6 @@ function rolldownPluginI18nextGettext(options) {
63
59
  };
64
60
  }
65
61
  //#endregion
66
- export { PROJECT_NAME, VIRTUAL_MODULE_ID, rolldownPluginI18nextGettext as default };
62
+ export { PROJECT_NAME, pluginI18nextGettext as default };
67
63
 
68
64
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["\nimport * as path from 'node:path'\n\nimport type { Plugin } from 'rolldown'\nimport { exactRegex, wrapArray, createJsonModule } from '@open-xchange/rolldown-utils'\nimport { glob } from 'tinyglobby'\nimport { parsePoFile, parseSourceFiles } from '@open-xchange/i18next-po-parser'\n\n// types ======================================================================\n\n/**\n * Configuration options for the plugin '@open-xchange/rolldown-plugin-i18next-gettext'.\n */\nexport interface RolldownPluginI18nextGettextOptions {\n\n /**\n * Glob pattern(s) for all PO files containing the translations. Matching PO\n * files will be converted to i18next translation catalogs when imported in\n * source code directly, and will be included in the virtual module providing\n * a dictionary of all translation catalogs, mapped by the base names of the\n * PO files.\n */\n poFiles: string | readonly string[]\n\n /**\n * Configuration for the source code scanner and POT file generator. If\n * omitted, no POT files will be generated.\n */\n potFiles?: {\n\n /**\n * Glob pattern(s) for all source files to be scanned for UI strings.\n *\n * @default\n * 'src/**\\/*.{js,jsx,ts,tsx}'\n */\n srcFiles?: string | readonly string[]\n\n /**\n * Path to and filename of the generated POT files, relative to the build\n * output directory. Should contain the placeholder `[NAMESPACE]` if the\n * project contains translation strings in different i18next namespaces.\n *\n * @default\n * '[NAMESPACE].pot'\n */\n outputPath?: string\n\n /**\n * The project name to be inserted into the POT files under the key\n * 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]`.\n */\n projectName: string\n }\n}\n\n// constants ==================================================================\n\n/**\n * Identifier of this plugin.\n */\nexport const PROJECT_NAME = '@open-xchange/rolldown-plugin-i18next-gettext'\n\n/**\n * Identifier of the virtual module providing all translation catalogs in a\n * single object.\n */\nexport const VIRTUAL_MODULE_ID = 'virtual:i18next-po-catalogs'\n\n// plugin =====================================================================\n\n/**\n * Rolldown plugin for using i18next with gettext `.po` files under the hood.\n *\n * @param options\n * Plugin configuration.\n *\n * @returns\n * The rolldown plugin object.\n */\nexport default function rolldownPluginI18nextGettext(options: RolldownPluginI18nextGettextOptions): Plugin {\n\n const RESOLVED_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`\n\n return {\n name: PROJECT_NAME,\n\n // recognize virtual module identifier\n resolveId: {\n filter: { id: exactRegex(VIRTUAL_MODULE_ID) },\n handler: () => RESOLVED_MODULE_ID,\n },\n\n // convert PO files to i18next JSON v4\n load: {\n filter: {\n id: [\n exactRegex(RESOLVED_MODULE_ID),\n ...wrapArray(options.poFiles),\n ],\n },\n async handler(id) {\n\n // virtual module: build single dictionary with all languages from PO files\n if (id === RESOLVED_MODULE_ID) {\n const map = {} as Record<string, Record<string, string>>\n for (const entry of await glob(options.poFiles)) {\n const basename = path.parse(entry).name\n map[basename] = await parsePoFile(entry)\n }\n return createJsonModule(map)\n }\n\n // read the PO file and convert it to i18next JSON\n return createJsonModule(await parsePoFile(id))\n },\n },\n\n // parse source files and create POT files per namespace\n async generateBundle() {\n\n // early exit if no POT files will be generated\n if (!options.potFiles) return\n const { srcFiles = 'src/**/*.{js,jsx,ts,tsx}', outputPath = '[NAMESPACE].pot', projectName } = options.potFiles\n\n // parse all source files and extract translations\n const catalogs = await parseSourceFiles({ files: srcFiles, project: projectName })\n\n // warn when writing multiple POT files to same file location\n if ((catalogs.size > 1) && !outputPath.includes('[NAMESPACE]')) {\n this.error('multiple POT files written to same file location (missing placeholder `[NAMESPACE]` in option potFile)')\n }\n\n // add all POT files as assets to the bundle\n for (const [namespace, source] of catalogs) {\n const fileName = outputPath.replaceAll('[NAMESPACE]', namespace)\n this.emitFile({ type: 'asset', fileName, source })\n }\n },\n }\n}\n"],"mappings":";;;;;;;;AA6DA,MAAa,eAAe;;;;;AAM5B,MAAa,oBAAoB;;;;;;;;;;AAajC,SAAwB,6BAA6B,SAAsD;CAEzG,MAAM,qBAAqB,KAAK;AAEhC,QAAO;EACL,MAAM;EAGN,WAAW;GACT,QAAQ,EAAE,IAAI,WAAW,kBAAkB,EAAE;GAC7C,eAAe;GAChB;EAGD,MAAM;GACJ,QAAQ,EACN,IAAI,CACF,WAAW,mBAAmB,EAC9B,GAAG,UAAU,QAAQ,QAAQ,CAC9B,EACF;GACD,MAAM,QAAQ,IAAI;AAGhB,QAAI,OAAO,oBAAoB;KAC7B,MAAM,MAAM,EAAE;AACd,UAAK,MAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,EAAE;MAC/C,MAAM,WAAW,KAAK,MAAM,MAAM,CAAC;AACnC,UAAI,YAAY,MAAM,YAAY,MAAM;;AAE1C,YAAO,iBAAiB,IAAI;;AAI9B,WAAO,iBAAiB,MAAM,YAAY,GAAG,CAAC;;GAEjD;EAGD,MAAM,iBAAiB;AAGrB,OAAI,CAAC,QAAQ,SAAU;GACvB,MAAM,EAAE,WAAW,4BAA4B,aAAa,mBAAmB,gBAAgB,QAAQ;GAGvG,MAAM,WAAW,MAAM,iBAAiB;IAAE,OAAO;IAAU,SAAS;IAAa,CAAC;AAGlF,OAAK,SAAS,OAAO,KAAM,CAAC,WAAW,SAAS,cAAc,CAC5D,MAAK,MAAM,yGAAyG;AAItH,QAAK,MAAM,CAAC,WAAW,WAAW,UAAU;IAC1C,MAAM,WAAW,WAAW,WAAW,eAAe,UAAU;AAChE,SAAK,SAAS;KAAE,MAAM;KAAS;KAAU;KAAQ,CAAC;;;EAGvD"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["\nimport * as path from 'node:path'\n\nimport type { Plugin } from 'rolldown'\nimport { prefixRegex, getRemainder, createJsonModule } from '@open-xchange/rolldown-utils'\nimport { glob } from 'tinyglobby'\nimport { parsePoFile, parseSourceFiles } from '@open-xchange/i18next-po-parser'\n\nimport type { Namespace } from 'virtual:i18next-namespace:*'\n\n// types ======================================================================\n\nexport type Globs = string | readonly string[]\n\n/**\n * Configuration options for the plugin '@open-xchange/rolldown-plugin-i18next-gettext'.\n */\nexport interface PluginI18nextGettextOptions {\n\n /**\n * Glob pattern(s) for all PO files to be converted to i18next translation\n * catalogs, mapped by i18next namespace identifiers.\n */\n poFiles: Record<string, Globs>\n\n /**\n * Glob pattern(s) for all source files to be scanned for UI strings for the\n * POT file generator.\n *\n * @default\n * 'src/**\\/*.{js,jsx,ts,tsx}'\n */\n srcFiles?: Globs\n\n /**\n * Path to and filename of the generated POT files, relative to the build\n * output directory. Must contain the placeholder `[NAMESPACE]` if the\n * project contains translation strings in different i18next namespaces.\n *\n * @default\n * '[NAMESPACE].pot'\n */\n potFiles?: string\n\n /**\n * The project name to be inserted into the POT files under the key\n * 'Project-Id-Version'. May contain the placeholder `[NAMESPACE]`.\n */\n projectName: string\n}\n\n// constants ==================================================================\n\n/**\n * Identifier of this plugin.\n */\nexport const PROJECT_NAME = '@open-xchange/rolldown-plugin-i18next-gettext'\n\n// plugin =====================================================================\n\n/**\n * Rolldown plugin for using i18next with gettext `.po` files under the hood.\n *\n * @param options\n * Plugin configuration.\n *\n * @returns\n * The rolldown plugin object.\n */\nexport default function pluginI18nextGettext(options: PluginI18nextGettextOptions): Plugin {\n\n // prefix for virtual modules generated by this plugin\n const VIRTUAL_MODULE_PREFIX = 'virtual:i18next-namespace:'\n\n return {\n name: PROJECT_NAME,\n\n // handle virtual module identifiers\n resolveId: {\n filter: { id: prefixRegex(VIRTUAL_MODULE_PREFIX) },\n handler: id => `\\0${id}`,\n },\n\n // convert PO files to i18next namespace records\n load: {\n filter: { id: prefixRegex(`\\0${VIRTUAL_MODULE_PREFIX}`) },\n async handler(id) {\n const key = getRemainder(id, `\\0${VIRTUAL_MODULE_PREFIX}`)\n if (!key) this.error('missing namespace specifier')\n const globs = options.poFiles[key]\n if (!globs) this.error(`invalid namespace specifier: ${key}`)\n const catalogs = Object.create(null) as Namespace\n const filePaths = await glob(globs)\n await Promise.all(filePaths.map(async filePath => {\n const basename = path.parse(filePath).name.replaceAll('_', '-')\n catalogs[basename] = await parsePoFile(filePath)\n }))\n return createJsonModule(catalogs)\n },\n },\n\n // parse source files and create POT files per namespace\n async generateBundle() {\n\n // resolve default options\n const { srcFiles = 'src/**/*.{js,jsx,ts,tsx}', potFiles = '[NAMESPACE].pot', projectName } = options\n\n // parse all source files and extract translations\n const catalogs = await parseSourceFiles({ files: srcFiles, project: projectName })\n\n // warn when writing multiple POT files to same file location\n if ((catalogs.size > 1) && !potFiles.includes('[NAMESPACE]')) {\n this.error('multiple POT files written to same file location (missing placeholder `[NAMESPACE]` in option potFile)')\n }\n\n // add all POT files as assets to the bundle\n for (const [namespace, source] of catalogs) {\n const fileName = potFiles.replaceAll('[NAMESPACE]', namespace)\n this.emitFile({ type: 'asset', fileName, source })\n }\n },\n }\n}\n"],"mappings":";;;;;;;;AAwDA,MAAa,eAAe;;;;;;;;;;AAa5B,SAAwB,qBAAqB,SAA8C;CAGzF,MAAM,wBAAwB;AAE9B,QAAO;EACL,MAAM;EAGN,WAAW;GACT,QAAQ,EAAE,IAAI,YAAY,sBAAsB,EAAE;GAClD,UAAS,OAAM,KAAK;GACrB;EAGD,MAAM;GACJ,QAAQ,EAAE,IAAI,YAAY,KAAK,wBAAwB,EAAE;GACzD,MAAM,QAAQ,IAAI;IAChB,MAAM,MAAM,aAAa,IAAI,KAAK,wBAAwB;AAC1D,QAAI,CAAC,IAAK,MAAK,MAAM,8BAA8B;IACnD,MAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAI,CAAC,MAAO,MAAK,MAAM,gCAAgC,MAAM;IAC7D,MAAM,WAAW,OAAO,OAAO,KAAK;IACpC,MAAM,YAAY,MAAM,KAAK,MAAM;AACnC,UAAM,QAAQ,IAAI,UAAU,IAAI,OAAM,aAAY;KAChD,MAAM,WAAW,KAAK,MAAM,SAAS,CAAC,KAAK,WAAW,KAAK,IAAI;AAC/D,cAAS,YAAY,MAAM,YAAY,SAAS;MAChD,CAAC;AACH,WAAO,iBAAiB,SAAS;;GAEpC;EAGD,MAAM,iBAAiB;GAGrB,MAAM,EAAE,WAAW,4BAA4B,WAAW,mBAAmB,gBAAgB;GAG7F,MAAM,WAAW,MAAM,iBAAiB;IAAE,OAAO;IAAU,SAAS;IAAa,CAAC;AAGlF,OAAK,SAAS,OAAO,KAAM,CAAC,SAAS,SAAS,cAAc,CAC1D,MAAK,MAAM,yGAAyG;AAItH,QAAK,MAAM,CAAC,WAAW,WAAW,UAAU;IAC1C,MAAM,WAAW,SAAS,WAAW,eAAe,UAAU;AAC9D,SAAK,SAAS;KAAE,MAAM;KAAS;KAAU;KAAQ,CAAC;;;EAGvD"}
package/dist/virtual.d.ts CHANGED
@@ -1,18 +1,18 @@
1
1
 
2
- declare module '*.po' {
2
+ declare module 'virtual:i18next-namespace:*' {
3
3
 
4
+ /**
5
+ * Shape of an i18next JSON translation catalog (mapping from source keys to
6
+ * translations) for a single language, converted from a gettext PO file.
7
+ */
4
8
  export type Catalog = Record<string, string>
5
9
 
6
- const catalog: Catalog
7
- export default catalog
8
- }
9
-
10
- declare module 'virtual:i18next-po-catalogs' {
11
-
12
- // eslint-disable-next-line n/no-missing-import
13
- import { Catalog } from 'test.po'
14
- export { Catalog }
10
+ /**
11
+ * Shape of an i18next JSON translation namespace (mapping from language
12
+ * codes to translation catalogs), converted from multiple gettext PO files.
13
+ */
14
+ export type Namespace = Record<string, Catalog>
15
15
 
16
- const catalogs: Record<string, Catalog>
17
- export default catalogs
16
+ const namespace: Namespace
17
+ export default namespace
18
18
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@open-xchange/rolldown-plugin-i18next-gettext",
3
- "version": "1.0.1",
4
- "description": "Rolldown integration of i18next using gettext",
3
+ "version": "1.1.0",
4
+ "description": "Rolldown integration of i18next using gettext PO files",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://gitlab.open-xchange.com/fspd/commons/plugins.git",
@@ -34,9 +34,9 @@
34
34
  "rolldown": "^1.0.0-rc.15",
35
35
  "tsdown": "^0.21.7",
36
36
  "vitest": "^4.1.3",
37
- "@open-xchange/vitest-plugin-utils": "^0.0.3",
38
- "@open-xchange/tsconfig": "^0.0.3",
39
37
  "@open-xchange/linter-presets": "^1.22.0",
38
+ "@open-xchange/tsconfig": "^0.0.3",
39
+ "@open-xchange/vitest-plugin-utils": "^1.0.0",
40
40
  "@open-xchange/vitest-plugin-extended": "^1.6.0"
41
41
  },
42
42
  "peerDependencies": {