@open-xchange/vite-plugin-i18next-gettext 0.0.1
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/.vscode/settings.json +3 -0
- package/CHANGELOG.md +5 -0
- package/README.md +92 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +74 -0
- package/package.json +46 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @open-xchange/vite-plugin-i18next-gettext
|
|
2
|
+
|
|
3
|
+
A Vite plugin that allows using [i18next](https://www.i18next.com/) in source code, while using gettext with `.po` and `.pot` files under the hood.
|
|
4
|
+
|
|
5
|
+
This plugin has the following responsibilities:
|
|
6
|
+
|
|
7
|
+
- Vite's development server will convert `.po` files imported in source code to i18next-compatible JSON data on-the-fly.
|
|
8
|
+
- When building the project, `.po` files will be converted to and written as i18next-compatible JSON files into the bundle.
|
|
9
|
+
- When building the project, 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.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Vite
|
|
14
|
+
|
|
15
|
+
Add the plugin to your Vite configuration:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// vite.config.ts
|
|
19
|
+
|
|
20
|
+
import { defineConfig } from "vite" // or "vitest/config"
|
|
21
|
+
import i18nextPlugin from "@open-xchange/vite-plugin-i18next-gettext"
|
|
22
|
+
|
|
23
|
+
export default defineConfig(() => {
|
|
24
|
+
|
|
25
|
+
// ...
|
|
26
|
+
|
|
27
|
+
plugins: [
|
|
28
|
+
// ...
|
|
29
|
+
|
|
30
|
+
i18nextPlugin({
|
|
31
|
+
poFiles: "i18n/*.po",
|
|
32
|
+
srcFiles: "src/**/*.{js,jsx,ts,tsx}",
|
|
33
|
+
potFile: "main.pot",
|
|
34
|
+
projectName: "My Project",
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Options
|
|
41
|
+
|
|
42
|
+
| Name | Type | Default | Description |
|
|
43
|
+
| - | - | - | - |
|
|
44
|
+
| `poFiles` | `string\|string[]` | _required_ | Glob pattern(s) for all `.po` files containing the translations. |
|
|
45
|
+
| `srcFiles` | `string\|string[]` | _required_ | Glob pattern(s) for all source files to be scanned for UI strings. |
|
|
46
|
+
| `potFile` | `string` | _required_ | Path to the `.pot` file to be generated when building the project, relative to the build output directory. |
|
|
47
|
+
| `projectName` | `string` | _required_ | The project name to be inserted into the `.pot` file under the key "Project-Id-Version". |
|
|
48
|
+
|
|
49
|
+
### Source Code
|
|
50
|
+
|
|
51
|
+
Import the `.po` files directly when setting up i18next, for example:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// src/@types/globals.d.ts
|
|
55
|
+
|
|
56
|
+
declare module "*.po" {
|
|
57
|
+
import { ResourceKey } from "i18next"
|
|
58
|
+
const resource: ResourceKey
|
|
59
|
+
export default resource
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// src/hooks/useTranslation.ts
|
|
65
|
+
|
|
66
|
+
import i18next from "i18next"
|
|
67
|
+
import type { BackendModule, ReadCallback } from "i18next"
|
|
68
|
+
|
|
69
|
+
class POFileBackend implements BackendModule {
|
|
70
|
+
|
|
71
|
+
// type needs to be provided statically (expected by i18next runtime), and as instance prop (for typings only)
|
|
72
|
+
static readonly type = "backend"
|
|
73
|
+
readonly type = "backend"
|
|
74
|
+
|
|
75
|
+
init(): void {
|
|
76
|
+
// nothing to do
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async read(lang: string, _ns: string, cb: ReadCallback): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
const module = await import(`./i18n/${lang}.po`) as typeof import("*.po")
|
|
82
|
+
cb(null, module.default)
|
|
83
|
+
} catch (error) {
|
|
84
|
+
cb(error as Error, null)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
i18next.use(POFileBackend)
|
|
90
|
+
|
|
91
|
+
export default useTranslation
|
|
92
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export interface VitePluginI18nextGettextOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Glob pattern(s) for all `.po` files containing the translations.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* {
|
|
8
|
+
* poFiles: "i18n/*.po",
|
|
9
|
+
* // ...
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
poFiles: string | string[];
|
|
13
|
+
/**
|
|
14
|
+
* Glob pattern(s) for all source files to be scanned for UI strings.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* {
|
|
18
|
+
* srcFiles: "src/*.{js,ts}",
|
|
19
|
+
* // ...
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
srcFiles: string | string[];
|
|
23
|
+
/**
|
|
24
|
+
* Path to the `.pot` file to be generated when building the project,
|
|
25
|
+
* relative to the build output directory.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* {
|
|
29
|
+
* potFile: "i18n/main.pot",
|
|
30
|
+
* // ...
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
potFile: string;
|
|
34
|
+
/**
|
|
35
|
+
* The project name to be inserted into the `.pot` file under the key
|
|
36
|
+
* "Project-Id-Version".
|
|
37
|
+
*/
|
|
38
|
+
projectName: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const PROJECT_NAME = "@open-xchange/vite-plugin-i18next-gettext";
|
|
41
|
+
/**
|
|
42
|
+
* Vite plugin for using i18next with gettext `.po` files under the hood.
|
|
43
|
+
*
|
|
44
|
+
* @param options
|
|
45
|
+
* Plugin configuration.
|
|
46
|
+
*
|
|
47
|
+
* @returns
|
|
48
|
+
* The Vite plugin object.
|
|
49
|
+
*/
|
|
50
|
+
export default function vitePluginI18nextGettext(options: VitePluginI18nextGettextOptions): Plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) Open-Xchange GmbH, Germany <info@open-xchange.com>
|
|
3
|
+
*
|
|
4
|
+
* This program is proprietary software and licensed to you under Open-Xchange
|
|
5
|
+
* GmbH's Software License Agreement.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import pm from "picomatch";
|
|
9
|
+
import converter from "gettext-converter";
|
|
10
|
+
import { transform as Transformer } from "i18next-parser";
|
|
11
|
+
import * as vfs from "vinyl-fs";
|
|
12
|
+
// constants ==================================================================
|
|
13
|
+
export const PROJECT_NAME = "@open-xchange/vite-plugin-i18next-gettext";
|
|
14
|
+
// plugin =====================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Vite plugin for using i18next with gettext `.po` files under the hood.
|
|
17
|
+
*
|
|
18
|
+
* @param options
|
|
19
|
+
* Plugin configuration.
|
|
20
|
+
*
|
|
21
|
+
* @returns
|
|
22
|
+
* The Vite plugin object.
|
|
23
|
+
*/
|
|
24
|
+
export default function vitePluginI18nextGettext(options) {
|
|
25
|
+
return {
|
|
26
|
+
name: PROJECT_NAME,
|
|
27
|
+
// register esbuild loader for `.po` files (load as plain text)
|
|
28
|
+
config: () => ({
|
|
29
|
+
optimizeDeps: {
|
|
30
|
+
esbuildOptions: {
|
|
31
|
+
loader: { ".po": "text" },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
// convert `.po` files to i18next JSON v4
|
|
36
|
+
async load(id) {
|
|
37
|
+
if (!pm.isMatch(id, options.poFiles)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// read the `.po` file and convert it to i18next JSON
|
|
41
|
+
const data = await readFile(id, { encoding: "utf8" });
|
|
42
|
+
const json = converter.po2i18next(data, { compatibilityJSON: "v4" });
|
|
43
|
+
// generate a module that exports a _stringified_ JSON object
|
|
44
|
+
const code = `export default ${JSON.stringify(json)};`;
|
|
45
|
+
return { code, map: { mappings: "" } };
|
|
46
|
+
},
|
|
47
|
+
// parse source files and create a `.pot` file
|
|
48
|
+
async generateBundle() {
|
|
49
|
+
// parse all source files and collect the translatable strings
|
|
50
|
+
const json = await new Promise((resolve, reject) => {
|
|
51
|
+
// the transformer instance that parses all source files, and assembles the translation catalogue
|
|
52
|
+
const transformer = new Transformer({
|
|
53
|
+
failOnWarnings: true,
|
|
54
|
+
contextSeparator: "",
|
|
55
|
+
keySeparator: false,
|
|
56
|
+
lineEnding: "lf",
|
|
57
|
+
namespaceSeparator: false,
|
|
58
|
+
// locales to generate catalogue for
|
|
59
|
+
locales: ["en"],
|
|
60
|
+
});
|
|
61
|
+
// warnings and errors
|
|
62
|
+
transformer.on("warning", (message) => reject(new Error(message)));
|
|
63
|
+
transformer.on("error", (message) => reject(new Error(message)));
|
|
64
|
+
// fulfil promise with the resulting JSON data
|
|
65
|
+
transformer.on("data", (file) => resolve(file.contents.toString("utf8")));
|
|
66
|
+
// pipe all source files to transformer
|
|
67
|
+
vfs.src(options.srcFiles).pipe(transformer);
|
|
68
|
+
});
|
|
69
|
+
// convert JSON data to `.pot` file
|
|
70
|
+
const source = converter.i18next2po("en", json, { project: options.projectName, compatibilityJSON: "v4" });
|
|
71
|
+
this.emitFile({ type: "asset", fileName: options.potFile, source });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-xchange/vite-plugin-i18next-gettext",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Vite integration of i18next using gettext",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://gitlab.open-xchange.com/fspd/npm-packages/vite-plugin-i18next-gettext"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": "18.18.0 || ^20.9.0 || >=21.1.0"
|
|
11
|
+
},
|
|
12
|
+
"packageManager": "yarn@4.3.1",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": "./dist/index.js",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepare": "husky",
|
|
17
|
+
"prepack": "yarn build && yarn lint",
|
|
18
|
+
"build": "npx --yes rimraf dist && tsc",
|
|
19
|
+
"lint": "eslint ."
|
|
20
|
+
},
|
|
21
|
+
"lint-staged": {
|
|
22
|
+
"*.{js,ts,json}": "yarn lint"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@open-xchange/linter-presets": "0.1.9",
|
|
26
|
+
"@types/node": "20.14.12",
|
|
27
|
+
"@types/vinyl-fs": "3.0.5",
|
|
28
|
+
"eslint": "9.7.0",
|
|
29
|
+
"husky": "9.1.2",
|
|
30
|
+
"typescript": "5.5.4",
|
|
31
|
+
"vinyl": "3.0.0",
|
|
32
|
+
"vite": "5.3.5"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"vite": "^5.3"
|
|
36
|
+
},
|
|
37
|
+
"resolutions": {
|
|
38
|
+
"semver": "^7.6.2"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"gettext-converter": "1.3.0",
|
|
42
|
+
"i18next-parser": "9.0.0",
|
|
43
|
+
"picomatch": "4.0.2",
|
|
44
|
+
"vinyl-fs": "4.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|