@intlify/unplugin-vue-i18n 0.0.0 → 0.1.2

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,3 +1,30 @@
1
+
2
+ ## @intlify/unplugin-vue-i18n@0.1.2 (2022-05-06)
3
+
4
+ #### :bug: Bug Fixes
5
+ * [#115](https://github.com/intlify/bundle-tools/pull/115) fix(unplugin-vue-i18n): export d.ts files ([@kazupon](https://github.com/kazupon))
6
+
7
+ #### Committers: 1
8
+ - kazuya kawaguchi ([@kazupon](https://github.com/kazupon))
9
+
10
+
11
+ ## @intlify/unplugin-vue-i18n@0.1.1 (2022-05-06)
12
+
13
+ #### :bug: Bug Fixes
14
+ * [#114](https://github.com/intlify/bundle-tools/pull/114) fix(unplugin-vue-i18n): export types ([@kazupon](https://github.com/kazupon))
15
+
16
+ #### Committers: 1
17
+ - kazuya kawaguchi ([@kazupon](https://github.com/kazupon))
18
+
19
+
20
+ ## @intlify/unplugin-vue-i18n@0.1.0 (2022-05-06)
21
+
22
+ #### :star: Features
23
+ * [#113](https://github.com/intlify/bundle-tools/pull/113) feat: unplugin basic implementation for vue-i18n ([@kazupon](https://github.com/kazupon))
24
+
25
+ #### Committers: 1
26
+ - kazuya kawaguchi ([@kazupon](https://github.com/kazupon))
27
+
1
28
  ## @intlify/unplugin-vue-i18n@0.0.0 (2022-05-02)
2
29
 
3
30
  :tada: Initial release.
package/README.md CHANGED
@@ -1,8 +1,215 @@
1
- # @intlify/unpluing-vue-i18n
1
+ # @intlify/unplugin-vue-i18n
2
+
3
+ [![Lint](https://github.com/intlify/bundle-tools/actions/workflows/lint.yml/badge.svg)](https://github.com/intlify/bundle-tools/actions/workflows/lint.yml)
4
+ [![Test](https://github.com/intlify/bundle-tools/actions/workflows/test.yml/badge.svg)](https://github.com/intlify/bundle-tools/actions/workflows/test.yml)
5
+ [![npm](https://img.shields.io/npm/v/@intlify/unplugin-vue-i18n.svg?color=yellow)](https://www.npmjs.com/package/@intlify/unplugin-vue-i18n)
6
+
2
7
 
3
8
  unplugin for Vue I18n
4
9
 
5
- WIP:
10
+ ## 🌟 Features
11
+ - i18n resource pre-compilation
12
+ - i18n custom block
13
+ - i18n resource definition
14
+ - i18n resource importing
15
+ - Locale of i18n resource definition
16
+ - Locale of i18n resource definition for global scope
17
+ - i18n resource formatting
18
+
19
+
20
+ ## 💿 Installation
21
+
22
+ ```sh
23
+ npm i @intlify/unplugin-vue-i18n
24
+ ```
25
+
26
+ <details>
27
+ <summary>Vite</summary><br>
28
+
29
+ ```ts
30
+ // vite.config.ts
31
+ import vueI18n from 'unplugin-vue-i18n/vite'
32
+
33
+ export default defineConfig({
34
+ plugins: [
35
+ vueI18n({ /* options */ }),
36
+ ],
37
+ })
38
+ ```
39
+
40
+ <br></details>
41
+
42
+ <details>
43
+ <summary>Webpack</summary><br>
44
+
45
+ ```ts
46
+ const VueI18nPlugin = require('unplugin-vue-i18n/webpack')
47
+
48
+ // webpack.config.js
49
+ module.exports = {
50
+ /* ... */
51
+ plugins: [
52
+ VueI18nPlugin({ /* options */ })
53
+ ]
54
+ }
55
+ ```
56
+
57
+ <br></details>
58
+
59
+
60
+ ## 🔧 Options
61
+
62
+ ### `include`
63
+
64
+ - **Type:** `string | string[] | undefined`
65
+ - **Default:** `undefined`
66
+
67
+ A [minimatch](https://github.com/isaacs/minimatch) pattern, or array of patterns, you can specify a path to pre-compile i18n resources files. The extensions of i18n resources to be precompiled are as follows:
68
+
69
+ ```
70
+ - json
71
+ - json5
72
+ - yaml
73
+ - yml
74
+ ```
75
+
76
+ Note `json` resources matches this option, it will be handled **before the internal json plugin of Vite, and will not be processed afterwards**, else the option doesn't match, the Vite side will handle.
77
+
78
+ ### `forceStringify`
79
+
80
+ - **Type:** `boolean`
81
+ - **Default:** `false`
82
+
83
+ Whether pre-compile number and boolean values as message functions that return the string value.
84
+
85
+ For example, the following json resources:
86
+
87
+ ```json
88
+ {
89
+ "trueValue": true,
90
+ "falseValue": false,
91
+ "nullValue": null,
92
+ "numberValue": 1
93
+ }
94
+ ```
95
+
96
+ after pre-compiled (development):
97
+
98
+ ```js
99
+ export default {
100
+ "trueValue": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize(["true"])};fn.source="true";return fn;})(),
101
+ "falseValue": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize(["false"])};fn.source="false";return fn;})(),
102
+ "nullValue": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize(["null"])};fn.source="null";return fn;})(),
103
+ "numberValue": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize(["1"])};fn.source="1";return fn;})()
104
+ }
105
+ ```
106
+
107
+ ### `defaultSFCLang`
108
+
109
+ - **Type:** `string`
110
+ - **Default:** `'json'`
111
+
112
+ Specify the content for all your inlined `i18n` custom blocks on your `SFC`.
113
+
114
+ `defaultSFCLang` must have one of the following values:
115
+
116
+ ```
117
+ - json
118
+ - json5
119
+ - yaml
120
+ - yml
121
+ ```
122
+
123
+ On inlined `i18n` custom blocks that have specified the `lang` attribute, the `defaultSFCLang` is not applied.
124
+
125
+ For example, with `defaultSFCLang: "yaml"` or `defaultSFCLang: "yml"`, this custom block:
126
+ ```html
127
+ <i18n lang="yaml">
128
+ en:
129
+ hello: Hello
130
+ es:
131
+ hello: Hola
132
+ </i18n>
133
+ ```
134
+
135
+ and this another one, are equivalent:
136
+ ```html
137
+ <i18n>
138
+ en:
139
+ hello: Hello
140
+ es:
141
+ hello: Hola
142
+ </i18n>
143
+ ```
144
+
145
+ ### `globalSFCScope`
146
+
147
+ - **Type:** `boolean`
148
+ - **Default:** `undefined`
149
+
150
+ Whether to include all `i18n` custom blocks on your `SFC` on `global` scope.
151
+
152
+ If `true`, it will be applied to all inlined `i18n` or `imported` custom blocks.
153
+
154
+ **Warning**: beware enabling `globalSFCScope: true`, all `i18n` custom blocks in all your `SFC` will be on `global` scope.
155
+
156
+ For example, with `globalSFCScope: true`, this custom block:
157
+
158
+ ```html
159
+ <i18n lang="yaml" global>
160
+ en:
161
+ hello: Hello
162
+ es:
163
+ hello: Hola
164
+ </i18n>
165
+ ```
166
+
167
+ and this another one, are equivalent:
168
+
169
+ ```html
170
+ <i18n lang="yaml">
171
+ en:
172
+ hello: Hello
173
+ es:
174
+ hello: Hola
175
+ </i18n>
176
+ ```
177
+
178
+ You can also use `defaultSFCLang: "yaml"`, following with previous example, this another is also equivalent to previous ones:
179
+
180
+ ```html
181
+ <i18n>
182
+ en:
183
+ hello: Hello
184
+ es:
185
+ hello: Hola
186
+ </i18n>
187
+ ```
188
+
189
+ ### `bridge`
190
+
191
+ - **Type:** `boolean`
192
+ - **Default:** `false`
193
+
194
+ The mode to birdge the i18n custom block to work in both vue-i18n@v8.x and vue-i18n@v9.x environments.
195
+
196
+ To support in a smooth transition from vue-i18n@v8.x to vue-i18n@v9.x, we provide a mode that bundles the i18n custom block to be available in either version.
197
+
198
+ > ⚠️ Note that if you set `bridge: true`, the bundle size will increase. It is recommended to disable this mode after the migration from vue-i18n@v8.26 to vue-i18n@v9.x is completed.
199
+
200
+ ### `useClassComponent`
201
+
202
+ - **Type:** `boolean`
203
+ - **Default:** `false`
204
+
205
+ This option that to use i18n custom blocks in `vue-class-component`.
206
+
207
+
208
+ ## ✅ TODO
209
+ - [ ] Virtual resource importing
210
+ - [ ] Bundling optimizations
211
+ - [ ] i18n custom block birdge mode (for vite)
212
+ - [ ] Support nuxt
6
213
 
7
214
  ## 📜 Changelog
8
215
 
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { default, unplugin, PluginOptions, SFCLangFormat } from './lib/index'
package/lib/index.cjs ADDED
@@ -0,0 +1,352 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const unplugin$1 = require('unplugin');
6
+ const pathe = require('pathe');
7
+ const createDebug = require('debug');
8
+ const fg = require('fast-glob');
9
+ const shared = require('@intlify/shared');
10
+ const pluginutils = require('@rollup/pluginutils');
11
+ const bundleUtils = require('@intlify/bundle-utils');
12
+ const compilerSfc = require('@vue/compiler-sfc');
13
+ const JSON5 = require('json5');
14
+ const yaml = require('js-yaml');
15
+ const fs = require('fs');
16
+
17
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
18
+
19
+ const createDebug__default = /*#__PURE__*/_interopDefaultLegacy(createDebug);
20
+ const fg__default = /*#__PURE__*/_interopDefaultLegacy(fg);
21
+ const JSON5__default = /*#__PURE__*/_interopDefaultLegacy(JSON5);
22
+ const yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
23
+
24
+ function parseVueRequest(id) {
25
+ const [filename, rawQuery] = id.split(`?`, 2);
26
+ const params = new URLSearchParams(rawQuery);
27
+ const ret = {};
28
+ const langPart = Object.keys(Object.fromEntries(params)).find((key) => /lang\./i.test(key));
29
+ ret.vue = params.has("vue");
30
+ ret.global = params.has("global");
31
+ ret.src = params.has("src");
32
+ ret.raw = params.has("raw");
33
+ if (params.has("type")) {
34
+ ret.type = params.get("type");
35
+ }
36
+ if (params.has("blockType")) {
37
+ ret.blockType = params.get("blockType");
38
+ }
39
+ if (params.has("index")) {
40
+ ret.index = Number(params.get("index"));
41
+ }
42
+ if (params.has("locale")) {
43
+ ret.locale = params.get("locale");
44
+ }
45
+ if (langPart) {
46
+ const [, lang] = langPart.split(".");
47
+ ret.lang = lang;
48
+ } else if (params.has("lang")) {
49
+ ret.lang = params.get("lang");
50
+ }
51
+ if (params.has("issuerPath")) {
52
+ ret.issuerPath = params.get("issuerPath");
53
+ }
54
+ return {
55
+ filename,
56
+ query: ret
57
+ };
58
+ }
59
+
60
+ function createBridgeCodeGenerator(source, query) {
61
+ return () => {
62
+ const data = convert(source, query.lang);
63
+ let value = JSON.parse(data);
64
+ if (shared.isString(query.locale)) {
65
+ value = Object.assign({}, { [query.locale]: value });
66
+ }
67
+ return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/\\/g, "\\\\").replace(/\u0027/g, "\\u0027");
68
+ };
69
+ }
70
+ function convert(source, lang) {
71
+ switch (lang) {
72
+ case "yaml":
73
+ case "yml":
74
+ const data = yaml__default.load(source);
75
+ return JSON.stringify(data, void 0, " ");
76
+ case "json5":
77
+ return JSON.stringify(JSON5__default.parse(source));
78
+ default:
79
+ return source;
80
+ }
81
+ }
82
+
83
+ function warn(...args) {
84
+ console.warn(`[unplugin-vue-i18n] `, ...args);
85
+ }
86
+ function error(...args) {
87
+ console.error(`[unplugin-vue-i18n] `, ...args);
88
+ }
89
+ async function getRaw(path) {
90
+ return fs.promises.readFile(path, { encoding: "utf-8" });
91
+ }
92
+ function raiseError(message) {
93
+ throw new Error(`[unplugin-vue-i18n] ${message}`);
94
+ }
95
+
96
+ const INTLIFY_BUNDLE_IMPORT_ID = "@intlify/unplugin-vue-i18n/messages";
97
+ const INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID = "@intlify/vite-plugin-vue-i18n/messages";
98
+ const debug = createDebug__default("unplugin-vue-i18n");
99
+ const unplugin = unplugin$1.createUnplugin((options = {}, meta) => {
100
+ debug("plugin options:", options, meta.framework);
101
+ if (!["vite", "webpack"].includes(meta.framework)) {
102
+ raiseError(`This plugin is supported 'vite' and 'webpack' only`);
103
+ }
104
+ let include = options.include;
105
+ if (include) {
106
+ if (shared.isArray(include)) {
107
+ include = include.map((item) => pathe.normalize(item));
108
+ } else if (shared.isString(include)) {
109
+ include = pathe.normalize(include);
110
+ }
111
+ }
112
+ const filter = pluginutils.createFilter(include);
113
+ const forceStringify = !!options.forceStringify;
114
+ const defaultSFCLang = shared.isString(options.defaultSFCLang) ? options.defaultSFCLang : "json";
115
+ const globalSFCScope = !!options.globalSFCScope;
116
+ const useClassComponent = !!options.useClassComponent;
117
+ const bridge = !!options.bridge;
118
+ debug("bridge", bridge);
119
+ let isProduction = false;
120
+ let sourceMap = false;
121
+ return {
122
+ name: "unplugin-vue-i18n",
123
+ enforce: meta.framework === "vite" ? "pre" : "post",
124
+ vite: {
125
+ configResolved(config) {
126
+ isProduction = config.isProduction;
127
+ sourceMap = config.command === "build" ? !!config.build.sourcemap : false;
128
+ debug(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
129
+ const jsonPlugin = config.plugins.find((p) => p.name === "vite:json");
130
+ if (jsonPlugin) {
131
+ const orgTransform = jsonPlugin.transform;
132
+ jsonPlugin.transform = async function(code, id) {
133
+ if (!/\.json$/.test(id) || filter(id)) {
134
+ return;
135
+ }
136
+ const { query } = parseVueRequest(id);
137
+ if (query.vue) {
138
+ return;
139
+ }
140
+ debug("org json plugin");
141
+ return orgTransform.apply(this, [code, id]);
142
+ };
143
+ }
144
+ }
145
+ },
146
+ webpack(compiler) {
147
+ isProduction = compiler.options.mode !== "development";
148
+ sourceMap = !!compiler.options.devtool;
149
+ debug(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
150
+ if (compiler.options.module) {
151
+ compiler.options.module.rules.push({
152
+ test: /\.(json5?|ya?ml)$/,
153
+ type: "javascript/auto",
154
+ exclude: include
155
+ });
156
+ }
157
+ },
158
+ resolveId(id, importer) {
159
+ debug("resolveId", id, importer);
160
+ if (id === INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID) {
161
+ warn(`deprected '${INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID}', you should switch to '${INTLIFY_BUNDLE_IMPORT_ID}'`);
162
+ return id;
163
+ }
164
+ if (id === INTLIFY_BUNDLE_IMPORT_ID) {
165
+ return id;
166
+ }
167
+ },
168
+ async load(id) {
169
+ debug("load", id);
170
+ const { query } = parseVueRequest(id);
171
+ if ([INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID, INTLIFY_BUNDLE_IMPORT_ID].includes(id) && include) {
172
+ let resourcePaths = [];
173
+ const includePaths = shared.isArray(include) ? include : [include];
174
+ for (const inc of includePaths) {
175
+ resourcePaths = [...resourcePaths, ...await fg__default(inc)];
176
+ }
177
+ resourcePaths = resourcePaths.filter((el, pos) => resourcePaths.indexOf(el) === pos);
178
+ const code = await generateBundleResources(resourcePaths, query, isProduction, {
179
+ forceStringify,
180
+ bridge,
181
+ useClassComponent
182
+ });
183
+ return {
184
+ code,
185
+ map: { mappings: "" }
186
+ };
187
+ }
188
+ },
189
+ async transform(code, id) {
190
+ const { filename, query } = parseVueRequest(id);
191
+ debug("transform", id, JSON.stringify(query), filename);
192
+ debug("transform (code)", code);
193
+ let langInfo = defaultSFCLang;
194
+ let inSourceMap;
195
+ if (!query.vue) {
196
+ if (/\.(json5?|ya?ml)$/.test(id) && filter(id)) {
197
+ langInfo = pathe.parse(filename).ext;
198
+ const generate = /\.?json5?/.test(langInfo) ? bundleUtils.generateJSON : bundleUtils.generateYAML;
199
+ const parseOptions = getOptions(filename, isProduction, query, sourceMap, {
200
+ inSourceMap,
201
+ isGlobal: globalSFCScope,
202
+ useClassComponent,
203
+ bridge,
204
+ forceStringify
205
+ });
206
+ debug("parseOptions", parseOptions);
207
+ const { code: generatedCode, map } = generate(code, parseOptions, bridge ? createBridgeCodeGenerator(code, query) : void 0);
208
+ debug("generated code", generatedCode);
209
+ debug("sourcemap", map, sourceMap);
210
+ if (code === generatedCode)
211
+ return;
212
+ return {
213
+ code: generatedCode,
214
+ map: sourceMap ? map : { mappings: "" }
215
+ };
216
+ } else {
217
+ return {
218
+ code,
219
+ map: { mappings: "", version: "3", sources: [] }
220
+ };
221
+ }
222
+ } else {
223
+ if (isCustomBlock(query)) {
224
+ if (shared.isString(query.lang)) {
225
+ langInfo = query.src ? query.lang === "i18n" ? "json" : query.lang : query.lang;
226
+ } else if (defaultSFCLang) {
227
+ langInfo = defaultSFCLang;
228
+ }
229
+ debug("langInfo", langInfo);
230
+ const generate = /\.?json5?/.test(langInfo) ? bundleUtils.generateJSON : bundleUtils.generateYAML;
231
+ const parseOptions = getOptions(filename, isProduction, query, sourceMap, {
232
+ inSourceMap,
233
+ isGlobal: globalSFCScope,
234
+ useClassComponent,
235
+ bridge,
236
+ forceStringify
237
+ });
238
+ debug("parseOptions", parseOptions);
239
+ const source = await getCode(code, filename, sourceMap, query, meta.framework);
240
+ const { code: generatedCode, map } = generate(source, parseOptions, bridge ? createBridgeCodeGenerator(source, query) : void 0);
241
+ debug("generated code", generatedCode);
242
+ debug("sourcemap", map, sourceMap);
243
+ if (code === generatedCode)
244
+ return;
245
+ return {
246
+ code: generatedCode,
247
+ map: sourceMap ? map : { mappings: "" }
248
+ };
249
+ }
250
+ }
251
+ }
252
+ };
253
+ });
254
+ async function generateBundleResources(resources, query, isProduction, {
255
+ forceStringify = false,
256
+ isGlobal = false,
257
+ bridge = false,
258
+ useClassComponent = false
259
+ }) {
260
+ const codes = [];
261
+ for (const res of resources) {
262
+ debug(`${res} bundle loading ...`);
263
+ if (/\.(json5?|ya?ml)$/.test(res)) {
264
+ const { ext, name } = pathe.parse(res);
265
+ const source = await getRaw(res);
266
+ const generate = /json5?/.test(ext) ? bundleUtils.generateJSON : bundleUtils.generateYAML;
267
+ const parseOptions = getOptions(res, isProduction, {}, false, {
268
+ isGlobal,
269
+ useClassComponent,
270
+ bridge,
271
+ forceStringify
272
+ });
273
+ parseOptions.type = "bare";
274
+ const { code } = generate(source, parseOptions, bridge ? createBridgeCodeGenerator(source, query) : void 0);
275
+ debug("generated code", code);
276
+ codes.push(`${JSON.stringify(name)}: ${code}`);
277
+ }
278
+ }
279
+ return `export default {
280
+ ${codes.join(`,
281
+ `)}
282
+ }`;
283
+ }
284
+ async function getCode(source, filename, sourceMap, query, framework = "vite") {
285
+ const { index, issuerPath } = query;
286
+ if (!shared.isNumber(index)) {
287
+ raiseError(`unexpected index: ${index}`);
288
+ }
289
+ if (framework === "webpack") {
290
+ if (issuerPath) {
291
+ debug(`getCode (webpack) ${index} via issuerPath`, issuerPath);
292
+ return await getRaw(filename);
293
+ } else {
294
+ const result = compilerSfc.parse(await getRaw(filename), {
295
+ sourceMap,
296
+ filename
297
+ });
298
+ const block = result.descriptor.customBlocks[index];
299
+ if (block) {
300
+ const code = block.src ? await getRaw(block.src) : block.content;
301
+ debug(`getCode (webpack) ${index} from SFC`, code);
302
+ return code;
303
+ } else {
304
+ return source;
305
+ }
306
+ }
307
+ } else {
308
+ return source;
309
+ }
310
+ }
311
+ function isCustomBlock(query) {
312
+ return !shared.isEmptyObject(query) && "vue" in query && (query["type"] === "custom" || query["type"] === "i18n" || query["blockType"] === "i18n");
313
+ }
314
+ function getOptions(filename, isProduction, query, sourceMap, {
315
+ inSourceMap = void 0,
316
+ forceStringify = false,
317
+ isGlobal = false,
318
+ bridge = false,
319
+ useClassComponent = false
320
+ }) {
321
+ const mode = isProduction ? "production" : "development";
322
+ const baseOptions = {
323
+ filename,
324
+ sourceMap,
325
+ inSourceMap,
326
+ forceStringify,
327
+ useClassComponent,
328
+ bridge,
329
+ env: mode,
330
+ onWarn: (msg) => {
331
+ warn(`${filename} ${msg}`);
332
+ },
333
+ onError: (msg) => {
334
+ error(`${filename} ${msg}`);
335
+ }
336
+ };
337
+ if (isCustomBlock(query)) {
338
+ return shared.assign(baseOptions, {
339
+ type: "sfc",
340
+ locale: shared.isString(query.locale) ? query.locale : "",
341
+ isGlobal: isGlobal || !!query.global
342
+ });
343
+ } else {
344
+ return shared.assign(baseOptions, {
345
+ type: "plain",
346
+ isGlobal: false
347
+ });
348
+ }
349
+ }
350
+
351
+ exports["default"] = unplugin;
352
+ exports.unplugin = unplugin;
package/lib/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
- import type { PluginOptions } from './types';
2
- export declare const unplugin: import("unplugin").UnpluginInstance<PluginOptions>;
3
- export default unplugin;
1
+ import * as _unplugin from 'unplugin';
2
+ import { PluginOptions } from './types.js';
3
+ export { PluginOptions, SFCLangFormat } from './types.js';
4
+
5
+ declare const unplugin: _unplugin.UnpluginInstance<PluginOptions>;
6
+
7
+ export { unplugin as default, unplugin };
package/lib/index.mjs ADDED
@@ -0,0 +1,340 @@
1
+ import { createUnplugin } from 'unplugin';
2
+ import { normalize, parse } from 'pathe';
3
+ import createDebug from 'debug';
4
+ import fg from 'fast-glob';
5
+ import { isString, isArray, isEmptyObject, isNumber, assign } from '@intlify/shared';
6
+ import { createFilter } from '@rollup/pluginutils';
7
+ import { generateJSON, generateYAML } from '@intlify/bundle-utils';
8
+ import { parse as parse$1 } from '@vue/compiler-sfc';
9
+ import JSON5 from 'json5';
10
+ import yaml from 'js-yaml';
11
+ import { promises } from 'fs';
12
+
13
+ function parseVueRequest(id) {
14
+ const [filename, rawQuery] = id.split(`?`, 2);
15
+ const params = new URLSearchParams(rawQuery);
16
+ const ret = {};
17
+ const langPart = Object.keys(Object.fromEntries(params)).find((key) => /lang\./i.test(key));
18
+ ret.vue = params.has("vue");
19
+ ret.global = params.has("global");
20
+ ret.src = params.has("src");
21
+ ret.raw = params.has("raw");
22
+ if (params.has("type")) {
23
+ ret.type = params.get("type");
24
+ }
25
+ if (params.has("blockType")) {
26
+ ret.blockType = params.get("blockType");
27
+ }
28
+ if (params.has("index")) {
29
+ ret.index = Number(params.get("index"));
30
+ }
31
+ if (params.has("locale")) {
32
+ ret.locale = params.get("locale");
33
+ }
34
+ if (langPart) {
35
+ const [, lang] = langPart.split(".");
36
+ ret.lang = lang;
37
+ } else if (params.has("lang")) {
38
+ ret.lang = params.get("lang");
39
+ }
40
+ if (params.has("issuerPath")) {
41
+ ret.issuerPath = params.get("issuerPath");
42
+ }
43
+ return {
44
+ filename,
45
+ query: ret
46
+ };
47
+ }
48
+
49
+ function createBridgeCodeGenerator(source, query) {
50
+ return () => {
51
+ const data = convert(source, query.lang);
52
+ let value = JSON.parse(data);
53
+ if (isString(query.locale)) {
54
+ value = Object.assign({}, { [query.locale]: value });
55
+ }
56
+ return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029").replace(/\\/g, "\\\\").replace(/\u0027/g, "\\u0027");
57
+ };
58
+ }
59
+ function convert(source, lang) {
60
+ switch (lang) {
61
+ case "yaml":
62
+ case "yml":
63
+ const data = yaml.load(source);
64
+ return JSON.stringify(data, void 0, " ");
65
+ case "json5":
66
+ return JSON.stringify(JSON5.parse(source));
67
+ default:
68
+ return source;
69
+ }
70
+ }
71
+
72
+ function warn(...args) {
73
+ console.warn(`[unplugin-vue-i18n] `, ...args);
74
+ }
75
+ function error(...args) {
76
+ console.error(`[unplugin-vue-i18n] `, ...args);
77
+ }
78
+ async function getRaw(path) {
79
+ return promises.readFile(path, { encoding: "utf-8" });
80
+ }
81
+ function raiseError(message) {
82
+ throw new Error(`[unplugin-vue-i18n] ${message}`);
83
+ }
84
+
85
+ const INTLIFY_BUNDLE_IMPORT_ID = "@intlify/unplugin-vue-i18n/messages";
86
+ const INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID = "@intlify/vite-plugin-vue-i18n/messages";
87
+ const debug = createDebug("unplugin-vue-i18n");
88
+ const unplugin = createUnplugin((options = {}, meta) => {
89
+ debug("plugin options:", options, meta.framework);
90
+ if (!["vite", "webpack"].includes(meta.framework)) {
91
+ raiseError(`This plugin is supported 'vite' and 'webpack' only`);
92
+ }
93
+ let include = options.include;
94
+ if (include) {
95
+ if (isArray(include)) {
96
+ include = include.map((item) => normalize(item));
97
+ } else if (isString(include)) {
98
+ include = normalize(include);
99
+ }
100
+ }
101
+ const filter = createFilter(include);
102
+ const forceStringify = !!options.forceStringify;
103
+ const defaultSFCLang = isString(options.defaultSFCLang) ? options.defaultSFCLang : "json";
104
+ const globalSFCScope = !!options.globalSFCScope;
105
+ const useClassComponent = !!options.useClassComponent;
106
+ const bridge = !!options.bridge;
107
+ debug("bridge", bridge);
108
+ let isProduction = false;
109
+ let sourceMap = false;
110
+ return {
111
+ name: "unplugin-vue-i18n",
112
+ enforce: meta.framework === "vite" ? "pre" : "post",
113
+ vite: {
114
+ configResolved(config) {
115
+ isProduction = config.isProduction;
116
+ sourceMap = config.command === "build" ? !!config.build.sourcemap : false;
117
+ debug(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
118
+ const jsonPlugin = config.plugins.find((p) => p.name === "vite:json");
119
+ if (jsonPlugin) {
120
+ const orgTransform = jsonPlugin.transform;
121
+ jsonPlugin.transform = async function(code, id) {
122
+ if (!/\.json$/.test(id) || filter(id)) {
123
+ return;
124
+ }
125
+ const { query } = parseVueRequest(id);
126
+ if (query.vue) {
127
+ return;
128
+ }
129
+ debug("org json plugin");
130
+ return orgTransform.apply(this, [code, id]);
131
+ };
132
+ }
133
+ }
134
+ },
135
+ webpack(compiler) {
136
+ isProduction = compiler.options.mode !== "development";
137
+ sourceMap = !!compiler.options.devtool;
138
+ debug(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
139
+ if (compiler.options.module) {
140
+ compiler.options.module.rules.push({
141
+ test: /\.(json5?|ya?ml)$/,
142
+ type: "javascript/auto",
143
+ exclude: include
144
+ });
145
+ }
146
+ },
147
+ resolveId(id, importer) {
148
+ debug("resolveId", id, importer);
149
+ if (id === INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID) {
150
+ warn(`deprected '${INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID}', you should switch to '${INTLIFY_BUNDLE_IMPORT_ID}'`);
151
+ return id;
152
+ }
153
+ if (id === INTLIFY_BUNDLE_IMPORT_ID) {
154
+ return id;
155
+ }
156
+ },
157
+ async load(id) {
158
+ debug("load", id);
159
+ const { query } = parseVueRequest(id);
160
+ if ([INTLIFY_BUNDLE_IMPORT_DEPRECTED_ID, INTLIFY_BUNDLE_IMPORT_ID].includes(id) && include) {
161
+ let resourcePaths = [];
162
+ const includePaths = isArray(include) ? include : [include];
163
+ for (const inc of includePaths) {
164
+ resourcePaths = [...resourcePaths, ...await fg(inc)];
165
+ }
166
+ resourcePaths = resourcePaths.filter((el, pos) => resourcePaths.indexOf(el) === pos);
167
+ const code = await generateBundleResources(resourcePaths, query, isProduction, {
168
+ forceStringify,
169
+ bridge,
170
+ useClassComponent
171
+ });
172
+ return {
173
+ code,
174
+ map: { mappings: "" }
175
+ };
176
+ }
177
+ },
178
+ async transform(code, id) {
179
+ const { filename, query } = parseVueRequest(id);
180
+ debug("transform", id, JSON.stringify(query), filename);
181
+ debug("transform (code)", code);
182
+ let langInfo = defaultSFCLang;
183
+ let inSourceMap;
184
+ if (!query.vue) {
185
+ if (/\.(json5?|ya?ml)$/.test(id) && filter(id)) {
186
+ langInfo = parse(filename).ext;
187
+ const generate = /\.?json5?/.test(langInfo) ? generateJSON : generateYAML;
188
+ const parseOptions = getOptions(filename, isProduction, query, sourceMap, {
189
+ inSourceMap,
190
+ isGlobal: globalSFCScope,
191
+ useClassComponent,
192
+ bridge,
193
+ forceStringify
194
+ });
195
+ debug("parseOptions", parseOptions);
196
+ const { code: generatedCode, map } = generate(code, parseOptions, bridge ? createBridgeCodeGenerator(code, query) : void 0);
197
+ debug("generated code", generatedCode);
198
+ debug("sourcemap", map, sourceMap);
199
+ if (code === generatedCode)
200
+ return;
201
+ return {
202
+ code: generatedCode,
203
+ map: sourceMap ? map : { mappings: "" }
204
+ };
205
+ } else {
206
+ return {
207
+ code,
208
+ map: { mappings: "", version: "3", sources: [] }
209
+ };
210
+ }
211
+ } else {
212
+ if (isCustomBlock(query)) {
213
+ if (isString(query.lang)) {
214
+ langInfo = query.src ? query.lang === "i18n" ? "json" : query.lang : query.lang;
215
+ } else if (defaultSFCLang) {
216
+ langInfo = defaultSFCLang;
217
+ }
218
+ debug("langInfo", langInfo);
219
+ const generate = /\.?json5?/.test(langInfo) ? generateJSON : generateYAML;
220
+ const parseOptions = getOptions(filename, isProduction, query, sourceMap, {
221
+ inSourceMap,
222
+ isGlobal: globalSFCScope,
223
+ useClassComponent,
224
+ bridge,
225
+ forceStringify
226
+ });
227
+ debug("parseOptions", parseOptions);
228
+ const source = await getCode(code, filename, sourceMap, query, meta.framework);
229
+ const { code: generatedCode, map } = generate(source, parseOptions, bridge ? createBridgeCodeGenerator(source, query) : void 0);
230
+ debug("generated code", generatedCode);
231
+ debug("sourcemap", map, sourceMap);
232
+ if (code === generatedCode)
233
+ return;
234
+ return {
235
+ code: generatedCode,
236
+ map: sourceMap ? map : { mappings: "" }
237
+ };
238
+ }
239
+ }
240
+ }
241
+ };
242
+ });
243
+ async function generateBundleResources(resources, query, isProduction, {
244
+ forceStringify = false,
245
+ isGlobal = false,
246
+ bridge = false,
247
+ useClassComponent = false
248
+ }) {
249
+ const codes = [];
250
+ for (const res of resources) {
251
+ debug(`${res} bundle loading ...`);
252
+ if (/\.(json5?|ya?ml)$/.test(res)) {
253
+ const { ext, name } = parse(res);
254
+ const source = await getRaw(res);
255
+ const generate = /json5?/.test(ext) ? generateJSON : generateYAML;
256
+ const parseOptions = getOptions(res, isProduction, {}, false, {
257
+ isGlobal,
258
+ useClassComponent,
259
+ bridge,
260
+ forceStringify
261
+ });
262
+ parseOptions.type = "bare";
263
+ const { code } = generate(source, parseOptions, bridge ? createBridgeCodeGenerator(source, query) : void 0);
264
+ debug("generated code", code);
265
+ codes.push(`${JSON.stringify(name)}: ${code}`);
266
+ }
267
+ }
268
+ return `export default {
269
+ ${codes.join(`,
270
+ `)}
271
+ }`;
272
+ }
273
+ async function getCode(source, filename, sourceMap, query, framework = "vite") {
274
+ const { index, issuerPath } = query;
275
+ if (!isNumber(index)) {
276
+ raiseError(`unexpected index: ${index}`);
277
+ }
278
+ if (framework === "webpack") {
279
+ if (issuerPath) {
280
+ debug(`getCode (webpack) ${index} via issuerPath`, issuerPath);
281
+ return await getRaw(filename);
282
+ } else {
283
+ const result = parse$1(await getRaw(filename), {
284
+ sourceMap,
285
+ filename
286
+ });
287
+ const block = result.descriptor.customBlocks[index];
288
+ if (block) {
289
+ const code = block.src ? await getRaw(block.src) : block.content;
290
+ debug(`getCode (webpack) ${index} from SFC`, code);
291
+ return code;
292
+ } else {
293
+ return source;
294
+ }
295
+ }
296
+ } else {
297
+ return source;
298
+ }
299
+ }
300
+ function isCustomBlock(query) {
301
+ return !isEmptyObject(query) && "vue" in query && (query["type"] === "custom" || query["type"] === "i18n" || query["blockType"] === "i18n");
302
+ }
303
+ function getOptions(filename, isProduction, query, sourceMap, {
304
+ inSourceMap = void 0,
305
+ forceStringify = false,
306
+ isGlobal = false,
307
+ bridge = false,
308
+ useClassComponent = false
309
+ }) {
310
+ const mode = isProduction ? "production" : "development";
311
+ const baseOptions = {
312
+ filename,
313
+ sourceMap,
314
+ inSourceMap,
315
+ forceStringify,
316
+ useClassComponent,
317
+ bridge,
318
+ env: mode,
319
+ onWarn: (msg) => {
320
+ warn(`${filename} ${msg}`);
321
+ },
322
+ onError: (msg) => {
323
+ error(`${filename} ${msg}`);
324
+ }
325
+ };
326
+ if (isCustomBlock(query)) {
327
+ return assign(baseOptions, {
328
+ type: "sfc",
329
+ locale: isString(query.locale) ? query.locale : "",
330
+ isGlobal: isGlobal || !!query.global
331
+ });
332
+ } else {
333
+ return assign(baseOptions, {
334
+ type: "plain",
335
+ isGlobal: false
336
+ });
337
+ }
338
+ }
339
+
340
+ export { unplugin as default, unplugin };
package/lib/types.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+
package/lib/types.d.ts CHANGED
@@ -1,2 +1,11 @@
1
- export interface PluginOptions {
1
+ declare type SFCLangFormat = 'json' | 'json5' | 'yml' | 'yaml';
2
+ interface PluginOptions {
3
+ include?: string | string[];
4
+ forceStringify?: boolean;
5
+ defaultSFCLang?: SFCLangFormat;
6
+ globalSFCScope?: boolean;
7
+ bridge?: boolean;
8
+ useClassComponent?: boolean;
2
9
  }
10
+
11
+ export { PluginOptions, SFCLangFormat };
package/lib/types.mjs ADDED
@@ -0,0 +1 @@
1
+
package/lib/vite.cjs ADDED
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const index = require('./index.cjs');
4
+ require('unplugin');
5
+ require('pathe');
6
+ require('debug');
7
+ require('fast-glob');
8
+ require('@intlify/shared');
9
+ require('@rollup/pluginutils');
10
+ require('@intlify/bundle-utils');
11
+ require('@vue/compiler-sfc');
12
+ require('json5');
13
+ require('js-yaml');
14
+ require('fs');
15
+
16
+ const vite = index.unplugin.vite;
17
+
18
+ module.exports = vite;
package/lib/vite.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import * as vite from 'vite';
2
+ import { PluginOptions } from './types.js';
3
+
4
+ declare const _default: (options?: PluginOptions | undefined) => vite.Plugin;
5
+
6
+ export { _default as default };
package/lib/vite.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import { unplugin } from './index.mjs';
2
+ import 'unplugin';
3
+ import 'pathe';
4
+ import 'debug';
5
+ import 'fast-glob';
6
+ import '@intlify/shared';
7
+ import '@rollup/pluginutils';
8
+ import '@intlify/bundle-utils';
9
+ import '@vue/compiler-sfc';
10
+ import 'json5';
11
+ import 'js-yaml';
12
+ import 'fs';
13
+
14
+ const vite = unplugin.vite;
15
+
16
+ export { vite as default };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const index = require('./index.cjs');
4
+ require('unplugin');
5
+ require('pathe');
6
+ require('debug');
7
+ require('fast-glob');
8
+ require('@intlify/shared');
9
+ require('@rollup/pluginutils');
10
+ require('@intlify/bundle-utils');
11
+ require('@vue/compiler-sfc');
12
+ require('json5');
13
+ require('js-yaml');
14
+ require('fs');
15
+
16
+ const webpack = index.unplugin.webpack;
17
+
18
+ module.exports = webpack;
@@ -0,0 +1,6 @@
1
+ import * as webpack from 'webpack';
2
+ import { PluginOptions } from './types.js';
3
+
4
+ declare const _default: (options?: PluginOptions | undefined) => webpack.WebpackPluginInstance;
5
+
6
+ export { _default as default };
@@ -0,0 +1,16 @@
1
+ import { unplugin } from './index.mjs';
2
+ import 'unplugin';
3
+ import 'pathe';
4
+ import 'debug';
5
+ import 'fast-glob';
6
+ import '@intlify/shared';
7
+ import '@rollup/pluginutils';
8
+ import '@intlify/bundle-utils';
9
+ import '@vue/compiler-sfc';
10
+ import 'json5';
11
+ import 'js-yaml';
12
+ import 'fs';
13
+
14
+ const webpack = unplugin.webpack;
15
+
16
+ export { webpack as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlify/unplugin-vue-i18n",
3
- "version": "0.0.0",
3
+ "version": "0.1.2",
4
4
  "description": "unplugin for Vue I18n",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
@@ -24,38 +24,62 @@
24
24
  "dependencies": {
25
25
  "@intlify/bundle-utils": "next",
26
26
  "@intlify/shared": "next",
27
+ "@rollup/pluginutils": "^4.2.0",
28
+ "@vue/compiler-sfc": "^3.2.23",
27
29
  "debug": "^4.3.1",
28
30
  "fast-glob": "^3.2.5",
31
+ "js-yaml": "^4.1.0",
32
+ "json5": "^2.2.0",
33
+ "pathe": "^0.2.0",
29
34
  "source-map": "0.6.1",
30
35
  "unplugin": "^0.6.2"
31
36
  },
37
+ "devDependencies": {
38
+ "mlly": "^0.5.2",
39
+ "unbuild": "^0.7.4"
40
+ },
32
41
  "engines": {
33
42
  "node": ">= 12"
34
43
  },
35
44
  "files": [
36
45
  "lib",
37
- "index.mjs"
46
+ "*.d.ts"
38
47
  ],
39
48
  "homepage": "https://github.com/intlify/bundle-tools/blob/main/packages/unplugin-vue-i18n/README.md",
40
49
  "keywords": [
41
50
  "i18n",
42
51
  "plugin",
43
52
  "unplugin",
44
- "unjs",
53
+ "transform",
54
+ "webpack",
55
+ "vite",
45
56
  "vue",
46
57
  "vue-i18n"
47
58
  ],
48
59
  "license": "MIT",
49
- "main": "lib/index.js",
50
- "module": "./index.mjs",
51
- "types": "lib/index.d.ts",
60
+ "main": "lib/index.cjs",
61
+ "module": "./lib/index.mjs",
62
+ "types": "./index.d.ts",
52
63
  "exports": {
53
64
  ".": {
54
- "import": "./index.mjs",
55
- "require": "./lib/index.js"
65
+ "require": "./lib/index.cjs",
66
+ "import": "./lib/index.mjs",
67
+ "types": "./index.d.ts"
68
+ },
69
+ "./vite": {
70
+ "require": "./lib/vite.cjs",
71
+ "import": "./lib/vite.mjs",
72
+ "types": "./vite.d.ts"
73
+ },
74
+ "./webpack": {
75
+ "require": "./lib/webpack.cjs",
76
+ "import": "./lib/webpack.mjs",
77
+ "types": "./webpack.d.ts"
78
+ },
79
+ "./types": {
80
+ "types": "./types.d.ts"
56
81
  },
57
82
  "./lib/*": "./lib/*",
58
- "./index.mjs": "./index.mjs",
59
83
  "./package.json": "./package.json"
60
84
  },
61
85
  "repository": {
@@ -64,10 +88,16 @@
64
88
  "directory": "packages/unplugin-vue-i18n"
65
89
  },
66
90
  "scripts": {
67
- "build": "tsc -p .",
91
+ "dev:vite": "vite examples/vite --config ./examples/vite/vite.config.ts",
92
+ "dev:webpack": "yarn build && webpack-dev-server --config ./examples/webpack/webpack.config.js --inline --hot",
93
+ "build": "unbuild",
94
+ "build:example": "npm-run-all \"build:example:*\"",
95
+ "build:example:vite": "vite build --config ./examples/vite/vite.config.ts --outDir ./examples/vite/dist",
96
+ "build:example:webpack": "yarn build && webpack --config ./examples/webpack/webpack.config.js",
68
97
  "clean": "npm-run-all \"clean:*\"",
69
98
  "clean:lib": "rm -rf ./lib",
70
- "watch": "tsc -p . --watch",
99
+ "test": "yarn test:e2e",
100
+ "test:e2e": "jest --runInBand --config ./jest.e2e.config.js",
71
101
  "changelog": "jiti ../../scripts/changelog.ts",
72
102
  "release": "jiti ../../scripts/release.ts"
73
103
  }
package/types.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { PluginOptions, SFCLangFormat } from './lib/types'
package/vite.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './lib/vite'
package/webpack.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { default } from './lib/webpack'
package/index.mjs DELETED
@@ -1 +0,0 @@
1
- export * from './lib/index.js'
package/lib/index.js DELETED
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unplugin = void 0;
4
- const unplugin_1 = require("unplugin");
5
- exports.unplugin = (0, unplugin_1.createUnplugin)((options = {}) => {
6
- console.log("I've borned in the world!");
7
- return {
8
- name: 'unplugin-vue-i18n'
9
- };
10
- });
11
- exports.default = exports.unplugin;
package/lib/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });