├── cli
├── bin
│ └── index.js
├── utils
│ ├── log.js
│ ├── mergeIi8nConfig.js
│ ├── autoi18n.config.js
│ ├── localeFile.js
│ └── baseUtils.js
├── command
│ ├── restore.js
│ ├── collect.js
│ └── initFileConf.js
└── index.js
├── .DS_Store
├── core
├── index.js
├── utils
│ ├── cacheCommentHtml.js
│ ├── cacheI18nField.js
│ ├── cacheCommentJs.js
│ └── baseUtils.js
├── restore
│ └── index.js
└── transform
│ ├── index.js
│ ├── transformReact.js
│ ├── transformJs.js
│ ├── transform.js
│ ├── transformVue.js
│ └── ast.js
├── examples
├── react-autoi18n-cli
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── index.html
│ ├── src
│ │ ├── locales
│ │ │ ├── zh-cn.json
│ │ │ └── en-us.json
│ │ ├── setupTests.js
│ │ ├── index.css
│ │ ├── reportWebVitals.js
│ │ ├── index.js
│ │ ├── i18n.js
│ │ ├── App.css
│ │ ├── App.js
│ │ └── logo.svg
│ ├── config
│ │ ├── jest
│ │ │ ├── cssTransform.js
│ │ │ ├── babelTransform.js
│ │ │ └── fileTransform.js
│ │ ├── pnpTs.js
│ │ ├── getHttpsConfig.js
│ │ ├── paths.js
│ │ ├── modules.js
│ │ ├── env.js
│ │ └── webpackDevServer.config.js
│ ├── .gitignore
│ ├── autoi18n.config.js
│ ├── scripts
│ │ ├── test.js
│ │ ├── start.js
│ │ └── build.js
│ ├── README.md
│ └── package.json
├── react-autoi18n-loaders
│ ├── public
│ │ ├── robots.txt
│ │ ├── favicon.ico
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── index.html
│ ├── src
│ │ ├── locales
│ │ │ ├── zh-cn.json
│ │ │ └── en-us.json
│ │ ├── setupTests.js
│ │ ├── index.css
│ │ ├── reportWebVitals.js
│ │ ├── index.js
│ │ ├── i18n.js
│ │ ├── App.css
│ │ ├── App.js
│ │ └── logo.svg
│ ├── config
│ │ ├── jest
│ │ │ ├── cssTransform.js
│ │ │ ├── babelTransform.js
│ │ │ └── fileTransform.js
│ │ ├── pnpTs.js
│ │ ├── getHttpsConfig.js
│ │ ├── paths.js
│ │ ├── modules.js
│ │ ├── env.js
│ │ └── webpackDevServer.config.js
│ ├── .gitignore
│ ├── autoi18n.config.js
│ ├── scripts
│ │ ├── test.js
│ │ ├── start.js
│ │ └── build.js
│ ├── README.md
│ └── package.json
├── vue-autoi18n-cli
│ ├── babel.config.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── locales
│ │ │ ├── zh-cn.json
│ │ │ └── en-us.json
│ │ ├── main.js
│ │ ├── i18n.js
│ │ ├── App.vue
│ │ └── components
│ │ │ └── HelloWorld.vue
│ ├── .gitignore
│ ├── README.md
│ ├── autoi18n.config.js
│ ├── .prettierrc.js
│ ├── package.json
│ └── vue.config.js
└── vue-autoi18n-loaders
│ ├── babel.config.js
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ ├── src
│ ├── assets
│ │ └── logo.png
│ ├── locales
│ │ ├── zh-cn.json
│ │ └── en-us.json
│ ├── main.js
│ ├── i18n.js
│ └── App.vue
│ ├── .gitignore
│ ├── README.md
│ ├── autoi18n.config.js
│ ├── .prettierrc.js
│ ├── vue.config.js
│ └── package.json
├── loaders
└── index.js
├── .gitignore
├── package.json
├── README.md
└── autoi8nScheme.md
/cli/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('..')
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/.DS_Store
--------------------------------------------------------------------------------
/core/index.js:
--------------------------------------------------------------------------------
1 | exports.transform = require('./transform/index')
2 | exports.restore = require('./restore/index')
3 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-cli/public/favicon.ico
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-cli/src/assets/logo.png
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/favicon.ico
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/logo192.png
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-cli/public/logo512.png
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/favicon.ico
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/logo192.png
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/react-autoi18n-loaders/public/logo512.png
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-loaders/public/favicon.ico
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shaneyxs/autoi18n/HEAD/examples/vue-autoi18n-loaders/src/assets/logo.png
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/locales/zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文",
4 | "f9fb6a063d1856da86a06def2dc6b921": "英文"
5 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/locales/zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文",
4 | "f9fb6a063d1856da86a06def2dc6b921": "英文"
5 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/locales/zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文",
4 | "f9fb6a063d1856da86a06def2dc6b921": "英文"
5 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/locales/zh-cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "欢迎使用 autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "中文",
4 | "f9fb6a063d1856da86a06def2dc6b921": "英文"
5 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese",
4 | "f9fb6a063d1856da86a06def2dc6b921": "English"
5 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese",
4 | "f9fb6a063d1856da86a06def2dc6b921": "English"
5 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese",
4 | "f9fb6a063d1856da86a06def2dc6b921": "English"
5 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "1457a8cf081b42e8461e210209b9661c": "Welcome to use autoi18n",
3 | "a7bac2239fcdcb3a067903d8077c4a07": "Chinese",
4 | "f9fb6a063d1856da86a06def2dc6b921": "English"
5 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import i18n from '@/i18n'
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | i18n,
8 | render: (h) => h(App),
9 | }).$mount('#app')
10 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import i18n from '@/i18n'
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | i18n,
8 | render: (h) => h(App),
9 | }).$mount('#app')
10 |
--------------------------------------------------------------------------------
/cli/utils/log.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | module.exports = {
4 | info: msg => console.log(chalk.cyan(msg)),
5 | warning: msg => console.log(chalk.yellow(msg)),
6 | success: msg => console.log(chalk.green(msg)),
7 | error: msg => console.log(chalk.red(msg)),
8 | };
9 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom";
6 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom";
6 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/README.md:
--------------------------------------------------------------------------------
1 | # vue-autoi18n
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = (onPerfEntry) => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/README.md:
--------------------------------------------------------------------------------
1 | # vue-autoi18n
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/i18n.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | Vue.use(VueI18n)
5 |
6 | const messages = {}
7 | const files = require.context('./locales', true, /\.json$/)
8 |
9 | files.keys().forEach((key) => {
10 | const name = key.replace(/^\.\/(.*)\.\w+$/, '$1')
11 | messages[name] = files(key)
12 | })
13 |
14 | const i18n = new VueI18n({
15 | locale: 'zh-cn',
16 | fallbackLocale: 'zh-cn',
17 | messages,
18 | })
19 |
20 | export default i18n
21 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/i18n.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | Vue.use(VueI18n)
5 |
6 | const messages = {}
7 | const files = require.context('./locales', true, /\.json$/)
8 |
9 | files.keys().forEach((key) => {
10 | const name = key.replace(/^\.\/(.*)\.\w+$/, '$1')
11 | messages[name] = files(key)
12 | })
13 |
14 | const i18n = new VueI18n({
15 | locale: 'zh-cn',
16 | fallbackLocale: 'zh-cn',
17 | messages,
18 | })
19 |
20 | export default i18n
21 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById("root")
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/autoi18n.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | language: ['zh-cn', 'en-us'],
3 | modules: 'es6',
4 | entry: ['./src'],
5 | localePath: './src/locales',
6 | localeFileExt: '.json',
7 | extensions: [],
8 | exclude: ['./src/locales/*.{js,ts,json}'],
9 | ignoreMethods: ['i18n.t', '$t'],
10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'],
11 | i18nObjectMethod: 'i18n.t',
12 | i18nMethod: '$t',
13 | setMessageKey: false,
14 | i18nInstance: "import i18n from '@/i18n'",
15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' },
16 | };
17 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/autoi18n.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | language: ['zh-cn', 'en-us'],
3 | modules: 'es6',
4 | entry: ['./src'],
5 | localePath: './src/locales',
6 | localeFileExt: '.json',
7 | extensions: [],
8 | exclude: ['./src/locales/*.{js,ts,json}'],
9 | ignoreMethods: ['i18n.t', '$t'],
10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'],
11 | i18nObjectMethod: 'i18n.t',
12 | i18nMethod: '$t',
13 | setMessageKey: false,
14 | i18nInstance: "import i18n from '@/i18n'",
15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' },
16 | };
17 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/autoi18n.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | language: ['zh-cn', 'en-us'],
3 | modules: 'es6',
4 | entry: ['./src'],
5 | localePath: './src/locales',
6 | localeFileExt: '.json',
7 | extensions: [],
8 | exclude: ['./src/locales/*.{js,ts,json}'],
9 | ignoreMethods: ['i18n.get'],
10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'],
11 | i18nObjectMethod: 'i18n.get',
12 | i18nMethod: 'i18n.get',
13 | setMessageKey: false,
14 | i18nInstance: "import { i18n } from '~/i18n'",
15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' },
16 | };
17 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/autoi18n.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | language: ['zh-cn', 'en-us'],
3 | modules: 'es6',
4 | entry: ['./src'],
5 | localePath: './src/locales',
6 | localeFileExt: '.json',
7 | extensions: [],
8 | exclude: ['./src/locales/*.{js,ts,json}'],
9 | ignoreMethods: ['i18n.get'],
10 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'],
11 | i18nObjectMethod: 'i18n.get',
12 | i18nMethod: 'i18n.get',
13 | setMessageKey: false,
14 | i18nInstance: "import { i18n } from '~/i18n'",
15 | prettier: { singleQuote: true, trailingComma: 'es5', endOfLine: 'lf' },
16 | };
17 |
--------------------------------------------------------------------------------
/loaders/index.js:
--------------------------------------------------------------------------------
1 | const loaderUtils = require('loader-utils')
2 | const path = require('path')
3 | const fs = require('fs')
4 | const mergeIi8nConfig = require('../cli/utils/mergeIi8nConfig');
5 | const { transform } = require('../core/index')
6 | let messages = {}
7 |
8 | module.exports = function (source) {
9 | const configOptions = mergeIi8nConfig()
10 | // const options = loaderUtils.getOptions(this);
11 | let targetFile = { ext: path.extname(this.resourcePath), filePath: this.resourcePath }
12 | source = transform({ code: source, targetFile, options: configOptions, messages })
13 | messages = {}
14 | return source
15 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/index.js:
--------------------------------------------------------------------------------
1 | import { i18n } from "~/i18n";
2 | import React from "react";
3 | import ReactDOM from "react-dom";
4 | import "./index.css";
5 | import App from "./App";
6 | import reportWebVitals from "./reportWebVitals";
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById("root")
13 | );
14 |
15 | // If you want to start measuring performance in your app, pass a function
16 | // to log results (for example: reportWebVitals(console.log))
17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
18 | reportWebVitals();
19 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // prettier配置
3 | printWidth: 180, // 超过最大值换行
4 | tabWidth: 2, // 缩进字节数
5 | useTabs: false, // 缩进不使用tab,使用空格
6 | semi: false, // 句尾添加分号
7 | singleQuote: true, // 使用单引号代替双引号
8 | proseWrap: 'preserve', // 默认值。因为使用了一些折行敏感型的渲染器 而按照markdown文本样式进行折行
9 | arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时是否要有小括号。always:不省略括号
10 | jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
11 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
12 | htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空格敏感度
13 | endOfLine: 'auto', // 行结束
14 | // trailingComma: 'none' // 无尾随逗号
15 | };
16 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/i18n.js:
--------------------------------------------------------------------------------
1 | import intl from "react-intl-universal";
2 |
3 | const locales = {};
4 | const files = require.context("./locales", true, /\.json$/);
5 |
6 | files.keys().forEach((key) => {
7 | const name = key.replace(/^\.\/(.*)\.\w+$/, "$1");
8 | locales[name] = files(key);
9 | });
10 |
11 | const currentLocale = window.localStorage.getItem("lang") || "zh-cn";
12 | intl.init({
13 | currentLocale,
14 | locales,
15 | });
16 |
17 | // 切换语言
18 | export const changeLang = (lang) => {
19 | window.localStorage.setItem("lang", lang);
20 | window.location.reload();
21 | };
22 |
23 | export const i18n = intl;
24 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/i18n.js:
--------------------------------------------------------------------------------
1 | import intl from "react-intl-universal";
2 |
3 | const locales = {};
4 | const files = require.context("./locales", true, /\.json$/);
5 |
6 | files.keys().forEach((key) => {
7 | const name = key.replace(/^\.\/(.*)\.\w+$/, "$1");
8 | locales[name] = files(key);
9 | });
10 |
11 | const currentLocale = window.localStorage.getItem("lang") || "zh-cn";
12 | intl.init({
13 | currentLocale,
14 | locales,
15 | });
16 |
17 | // 切换语言
18 | export const changeLang = (lang) => {
19 | window.localStorage.setItem("lang", lang);
20 | window.location.reload();
21 | };
22 |
23 | export const i18n = intl;
24 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // prettier配置
3 | printWidth: 180, // 超过最大值换行
4 | tabWidth: 2, // 缩进字节数
5 | useTabs: false, // 缩进不使用tab,使用空格
6 | semi: false, // 句尾添加分号
7 | singleQuote: true, // 使用单引号代替双引号
8 | proseWrap: 'preserve', // 默认值。因为使用了一些折行敏感型的渲染器 而按照markdown文本样式进行折行
9 | arrowParens: 'always', // (x) => {} 箭头函数参数只有一个时是否要有小括号。always:不省略括号
10 | jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
11 | jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
12 | htmlWhitespaceSensitivity: 'ignore', // 指定HTML文件的全局空格敏感度
13 | endOfLine: 'auto', // 行结束
14 | // trailingComma: 'none' // 无尾随逗号
15 | };
16 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/jest/babelTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const babelJest = require('babel-jest');
4 |
5 | const hasJsxRuntime = (() => {
6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
7 | return false;
8 | }
9 |
10 | try {
11 | require.resolve('react/jsx-runtime');
12 | return true;
13 | } catch (e) {
14 | return false;
15 | }
16 | })();
17 |
18 | module.exports = babelJest.createTransformer({
19 | presets: [
20 | [
21 | require.resolve('babel-preset-react-app'),
22 | {
23 | runtime: hasJsxRuntime ? 'automatic' : 'classic',
24 | },
25 | ],
26 | ],
27 | babelrc: false,
28 | configFile: false,
29 | });
30 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/jest/babelTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const babelJest = require('babel-jest');
4 |
5 | const hasJsxRuntime = (() => {
6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
7 | return false;
8 | }
9 |
10 | try {
11 | require.resolve('react/jsx-runtime');
12 | return true;
13 | } catch (e) {
14 | return false;
15 | }
16 | })();
17 |
18 | module.exports = babelJest.createTransformer({
19 | presets: [
20 | [
21 | require.resolve('babel-preset-react-app'),
22 | {
23 | runtime: hasJsxRuntime ? 'automatic' : 'classic',
24 | },
25 | ],
26 | ],
27 | babelrc: false,
28 | configFile: false,
29 | });
30 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/core/utils/cacheCommentHtml.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 对html注释缓存 恢复处理
4 | */
5 | module.exports = {
6 | /**
7 | * 暂存html注释对象
8 | */
9 | commentsCache: {},
10 | /**
11 | * 先去掉html的注释 暂存注释
12 | */
13 | stash(sourceCode, options) {
14 | this.commentsCache = {}
15 | let commentsIndex = 0
16 | sourceCode = sourceCode.replace(//gm, (match, content) => {
17 | let commentsKey = `%%comment_html_${commentsIndex++}%%`
18 | this.commentsCache[commentsKey] = content
19 | return ``
20 | })
21 | return sourceCode
22 | },
23 | /**
24 | * 恢复之前删除的注释
25 | */
26 | restore(sourceCode, options) {
27 | sourceCode = sourceCode.replace(/%%comment_html_\d+%%/gim, (match) => {
28 | return this.commentsCache[match]
29 | });
30 | this.commentsCache = {} // 清除缓存
31 | return sourceCode
32 | }
33 | }
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
欢迎使用 autoi18n
4 |
8 |
9 |
10 |
11 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/cli/utils/mergeIi8nConfig.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const defaultConfig = require('./autoi18n.config')
4 | const log = require('./log')
5 |
6 | const cwdPath = process.cwd()
7 |
8 | module.exports = function mergeOptions(programOption) {
9 | const options = defaultConfig;
10 | const configFileName = programOption && programOption.config || 'autoi18n.config.js'
11 |
12 | const configFilePath = path.join(cwdPath, configFileName)
13 | // 读取 autoi18n.config.js 中设置的参数,然后并入 options
14 | if (fs.existsSync(configFilePath)) {
15 | let configurationFile = {}
16 | try {
17 | configurationFile = require(configFilePath)
18 | } catch (err) {
19 | log.warning(`请检查 ${configFileName} 配置文件是否正确\n`)
20 | }
21 |
22 | Object.assign(options, configurationFile)
23 | } else {
24 | log.warning(`配置文件 ${configFileName} 不存在\n采用默认配置`)
25 | }
26 |
27 | return options;
28 | };
29 |
--------------------------------------------------------------------------------
/core/restore/index.js:
--------------------------------------------------------------------------------
1 | const baseUtils = require('../utils/baseUtils')
2 | /**
3 | * 恢复不同的文件文案
4 | * @param {*} options.code 源代码
5 | * @param {*} options.targetFile 文件对象
6 | * @param {*} options.options 国际化配置对象
7 | * @param {*} options.messages 国际化字段对象
8 | * @returns code 经过恢复文案的代码
9 | */
10 | module.exports = function ({ code, targetFile, options, messages }) {
11 | let ignoreMethods = options.ignoreMethods
12 | // 转义字符串
13 | ignoreMethods = ignoreMethods.map((item) => baseUtils.stringRegEscape(item))
14 | const ident = ignoreMethods.join('|')
15 | code = code.replace(new RegExp(`(${ident})\\((['"\`])((((?!\\2|\\().)+))\\2[^(]*?\\)`, 'gm'), (match, method, sign , key) => {
16 | if (messages[key]) {
17 | return `${sign}${messages[key]}${sign}`
18 | }
19 | return match
20 | })
21 | // 删除import 实例对象
22 | // code = code.replace(new RegExp(baseUtils.stringRegEscape(options.i18nInstance), 'gm'), '')
23 | return code
24 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from "./logo.svg";
2 | import "./App.css";
3 | import { changeLang } from "~/i18n";
4 |
5 | function App() {
6 | const setLanguage = (e) => {
7 | changeLang(e.target.value)
8 | }
9 | return (
10 |
31 | );
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('1457a8cf081b42e8461e210209b9661c') }}
4 |
8 |
9 |
10 |
11 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/core/transform/index.js:
--------------------------------------------------------------------------------
1 | const transformVue = require('./transformVue')
2 | const transformReact = require('./transformReact')
3 | const transformJs = require('./transformJs')
4 |
5 | /**
6 | * 处理不同的文件转换
7 | * @param {*} options.code 源代码
8 | * @param {*} options.targetFile 文件对象
9 | * @param {*} options.options 国际化配置对象
10 | * @param {*} options.messages 国际化字段对象
11 | * @returns code 经过国际化的代码
12 | */
13 | module.exports = function ({ code, targetFile, options, messages }) {
14 | let data = ''
15 | if (targetFile.ext === '.vue') {
16 | // 处理vue文件
17 | data = transformVue({ code, file: targetFile, ext: targetFile.ext, options, messages })
18 | } else if (targetFile.ext === '.js' || targetFile.ext === '.ts') {
19 | // 处理js文件
20 | data = transformJs({ code, file: targetFile, ext: targetFile.ext, options, messages })
21 | } else if (targetFile.ext === '.jsx' || targetFile.ext === '.tsx') {
22 | // 处理react文件
23 | data = transformReact({ code, file: targetFile, ext: targetFile.ext, options, messages })
24 | }
25 | return data
26 | }
27 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/vue.config.js:
--------------------------------------------------------------------------------
1 |
2 | const isDev = process.env.DEPLOY_ENV === 'development' // 开发环境
3 | const isProd = process.env.DEPLOY_ENV === 'production' // 生产环境
4 | const isTest = process.env.DEPLOY_ENV === 'test' // 测试环境
5 | const isDeploy = isProd || isTest // 是否是部署环境
6 |
7 | module.exports = {
8 | parallel: false,
9 | productionSourceMap: isDev,
10 | lintOnSave: true,
11 | publicPath: isDeploy ? '' : '',
12 | assetsDir: 'static',
13 | css: {
14 | // 是否使用css分离插件 ExtractTextPlugin
15 | extract: isDeploy,
16 | // 开启 CSS source maps?
17 | sourceMap: false
18 | },
19 | chainWebpack: (config) => {
20 | // 配置自动国际化loader 无侵入式
21 | config.module
22 | .rule('autoi18n')
23 | .test(/\.(vue|(j|t)sx?)$/)
24 | .pre() // 这个必须加上 优先执行的loader 顺序一定要在use方法前面 否则会报找不到pre方法
25 | .use('autoi18n')
26 | .loader('autoi18n')
27 | .end()
28 | },
29 | devServer: {
30 | open: true, //是否自动弹出浏览器页面
31 | port: 8089, // 设置端口号
32 | https: false, //是否使用https协议
33 | hotOnly: false, //是否开启热更新
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-autoi18n",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "vue": "^2.6.11",
13 | "vue-i18n": "^8.25.0"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "~4.5.0",
17 | "@vue/cli-plugin-eslint": "~4.5.0",
18 | "@vue/cli-service": "~4.5.0",
19 | "babel-eslint": "^10.1.0",
20 | "eslint": "^6.7.2",
21 | "eslint-plugin-vue": "^6.2.2",
22 | "vue-template-compiler": "^2.6.11"
23 | },
24 | "eslintConfig": {
25 | "root": true,
26 | "env": {
27 | "node": true
28 | },
29 | "extends": [
30 | "plugin:vue/essential",
31 | "eslint:recommended"
32 | ],
33 | "parserOptions": {
34 | "parser": "babel-eslint"
35 | },
36 | "rules": {}
37 | },
38 | "browserslist": [
39 | "> 1%",
40 | "last 2 versions",
41 | "not dead"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-loaders/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-autoi18n",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "vue": "^2.6.11",
13 | "vue-i18n": "^8.25.0"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "~4.5.0",
17 | "@vue/cli-plugin-eslint": "~4.5.0",
18 | "@vue/cli-service": "~4.5.0",
19 | "autoi18n": "^1.0.6",
20 | "babel-eslint": "^10.1.0",
21 | "eslint": "^6.7.2",
22 | "eslint-plugin-vue": "^6.2.2",
23 | "vue-template-compiler": "^2.6.11"
24 | },
25 | "eslintConfig": {
26 | "root": true,
27 | "env": {
28 | "node": true
29 | },
30 | "extends": [
31 | "plugin:vue/essential",
32 | "eslint:recommended"
33 | ],
34 | "parserOptions": {
35 | "parser": "babel-eslint"
36 | },
37 | "rules": {}
38 | },
39 | "browserslist": [
40 | "> 1%",
41 | "last 2 versions",
42 | "not dead"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/core/utils/cacheI18nField.js:
--------------------------------------------------------------------------------
1 | const baseUtils = require('./baseUtils')
2 | /**
3 | * 对已设置国际化的字段进行缓存 恢复
4 | * 忽略已经设置了国际化的字符串 防止key为中文时 会重复设置
5 | */
6 | module.exports = {
7 | /**
8 | * 暂存国际化字符串对象
9 | */
10 | i18nFieldCache: {},
11 | /**
12 | * 先去掉国际化字符串 缓存
13 | */
14 | stash(sourceCode, options) {
15 | this.i18nFieldCache = {}
16 | let i18nFieldIndex = 0
17 | let ignoreMethods = options.ignoreMethods
18 | // 转义字符串
19 | ignoreMethods = ignoreMethods.map((item) => baseUtils.stringRegEscape(item))
20 | const ident = ignoreMethods.join('|')
21 | sourceCode = sourceCode.replace(new RegExp(`(${ident})\\([^(]+?\\)`, 'gm'), (match) => {
22 | let i18nFieldKey = `__i18n_field_${i18nFieldIndex++}__()`
23 | this.i18nFieldCache[i18nFieldKey] = match
24 | return i18nFieldKey
25 | })
26 | return sourceCode
27 | },
28 | /**
29 | * 恢复之前删除的国际化字符串
30 | */
31 | restore(sourceCode, options) {
32 | sourceCode = sourceCode.replace(/__i18n_field_\d+__\(\)/gm, (match) => {
33 | return this.i18nFieldCache[match]
34 | });
35 | this.i18nFieldCache = {} // 清除缓存
36 | return sourceCode
37 | }
38 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/App.js:
--------------------------------------------------------------------------------
1 | import { i18n } from "~/i18n";
2 | import logo from "./logo.svg";
3 | import "./App.css";
4 | import { changeLang } from "~/i18n";
5 |
6 | function App() {
7 | const setLanguage = (e) => {
8 | changeLang(e.target.value)
9 | }
10 | return (
11 |
32 | );
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/core/transform/transformReact.js:
--------------------------------------------------------------------------------
1 | const cacheCommentJs = require('../utils/cacheCommentJs')
2 | const cacheI18nField = require('../utils/cacheI18nField')
3 | const ast = require('./ast')
4 | const baseUtils = require('../utils/baseUtils')
5 |
6 | /**
7 | * 转换react
8 | * @param {*} options.code 源代码
9 | * @param {*} options.options 国际化配置对象
10 | * @param {*} options.file 文件对象
11 | * @param {*} options.messages 国际化字段对象
12 | * @param {*} options.ext 文件类型
13 | * @returns
14 | */
15 | module.exports = function ({ code, file, options, messages, ext = '.jsx' }) {
16 | // 复制一份国际化数据配置
17 | const oldMessages = JSON.stringify(messages)
18 | // 暂存注释 react 注释 就是js注释 ast替换的是字符串 所以可以不处理注释
19 | // code = cacheCommentJs.stash(code, options)
20 | // 暂存已经设置的国际化字段
21 | code = cacheI18nField.stash(code, options)
22 | // 转换react
23 | const lang = ['.ts', '.tsx'].includes(ext) ? 'ts' : 'js'
24 | code = ast({ code, file, options, messages, ext, codeType: 'jsx', lang })
25 | // 恢复注释
26 | // code = cacheCommentJs.restore(code, options)
27 | // 恢复已经设置的国际化字段
28 | code = cacheI18nField.restore(code, options)
29 | // 国际化数据发生变化才注入 证明该js有国际化字段
30 | if (oldMessages !== JSON.stringify(messages)) {
31 | // 注入实例
32 | code = baseUtils.injectInstance({ code, ext, options })
33 | }
34 | return code
35 | }
36 |
--------------------------------------------------------------------------------
/core/transform/transformJs.js:
--------------------------------------------------------------------------------
1 | const cacheCommentJs = require('../utils/cacheCommentJs')
2 | const cacheI18nField = require('../utils/cacheI18nField')
3 | const ast = require('./ast')
4 | const baseUtils = require('../utils/baseUtils')
5 |
6 | /**
7 | * 转换js
8 | * @param {*} options.code 源代码
9 | * @param {*} options.file 文件对象
10 | * @param {*} options.options 国际化配置对象
11 | * @param {*} options.messages 国际化字段对象
12 | * @param {*} options.codeType 代码类型
13 | * @param {*} options.ext 文件类型
14 | * @returns
15 | */
16 | module.exports = function ({ code, file, options, messages, lang, codeType = 'js', ext = '.js' }) {
17 | // 复制一份国际化数据配置
18 | const oldMessages = JSON.stringify(messages)
19 | // 暂存注释
20 | // code = cacheCommentJs.stash(code, options) ast替换的是字符串 所以可以不处理注释
21 | // 暂存已经设置的国际化字段
22 | code = cacheI18nField.stash(code, options)
23 | // 转换js
24 | lang = lang ? lang : ext === '.ts' ? 'ts' : 'js'
25 | code = ast({ code, file, options, messages, ext, codeType, lang })
26 | // 恢复注释
27 | // code = cacheCommentJs.restore(code, options)
28 | // 恢复已经设置的国际化字段
29 | code = cacheI18nField.restore(code, options)
30 | // 国际化数据发生变化才注入 证明该js有国际化字段
31 | if (oldMessages !== JSON.stringify(messages)) {
32 | // 注入实例
33 | code = baseUtils.injectInstance({ code, ext, options })
34 | }
35 | return code
36 | }
37 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const camelcase = require('camelcase');
5 |
6 | // This is a custom Jest transformer turning file imports into filenames.
7 | // http://facebook.github.io/jest/docs/en/webpack.html
8 |
9 | module.exports = {
10 | process(src, filename) {
11 | const assetFilename = JSON.stringify(path.basename(filename));
12 |
13 | if (filename.match(/\.svg$/)) {
14 | // Based on how SVGR generates a component name:
15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16 | const pascalCaseFilename = camelcase(path.parse(filename).name, {
17 | pascalCase: true,
18 | });
19 | const componentName = `Svg${pascalCaseFilename}`;
20 | return `const React = require('react');
21 | module.exports = {
22 | __esModule: true,
23 | default: ${assetFilename},
24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25 | return {
26 | $$typeof: Symbol.for('react.element'),
27 | type: 'svg',
28 | ref: ref,
29 | key: null,
30 | props: Object.assign({}, props, {
31 | children: ${assetFilename}
32 | })
33 | };
34 | }),
35 | };`;
36 | }
37 |
38 | return `module.exports = ${assetFilename};`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const camelcase = require('camelcase');
5 |
6 | // This is a custom Jest transformer turning file imports into filenames.
7 | // http://facebook.github.io/jest/docs/en/webpack.html
8 |
9 | module.exports = {
10 | process(src, filename) {
11 | const assetFilename = JSON.stringify(path.basename(filename));
12 |
13 | if (filename.match(/\.svg$/)) {
14 | // Based on how SVGR generates a component name:
15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16 | const pascalCaseFilename = camelcase(path.parse(filename).name, {
17 | pascalCase: true,
18 | });
19 | const componentName = `Svg${pascalCaseFilename}`;
20 | return `const React = require('react');
21 | module.exports = {
22 | __esModule: true,
23 | default: ${assetFilename},
24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25 | return {
26 | $$typeof: Symbol.for('react.element'),
27 | type: 'svg',
28 | ref: ref,
29 | key: null,
30 | props: Object.assign({}, props, {
31 | children: ${assetFilename}
32 | })
33 | };
34 | }),
35 | };`;
36 | }
37 |
38 | return `module.exports = ${assetFilename};`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/cli/utils/autoi18n.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * 需要国际化的语言种类
4 | */
5 | language: ['zh-cn', 'en-us'],
6 | /**
7 | * 国际化资源文件应用的 模块模式 根据这个模式 使用 module.exports 或者 export default
8 | * 如果localeFileExt 配置为json时 此配置不起效
9 | */
10 | modules: 'es6',
11 | /**
12 | * 需要国际化的目录
13 | */
14 | entry: ['./src'],
15 | /**
16 | * 国际化资源文件输出目录
17 | */
18 | localePath: './src/locales',
19 | /**
20 | * 国际化文件类型 默认 为 .json文件 支持.js和.json
21 | */
22 | localeFileExt: '.json',
23 | /**
24 | * 需要处理国际化的文件后缀
25 | */
26 | extensions: [],
27 | /**
28 | * 需要排除国际化的文件 glob模式数组
29 | */
30 | exclude: [],
31 | /**
32 | * 要忽略做国际化的方法
33 | */
34 | ignoreMethods: ['i18n.t', '$t'],
35 | /**
36 | * 要忽略做标签属性
37 | */
38 | ignoreTagAttr: ['class', 'style', 'src', 'href', 'width', 'height'],
39 | /**
40 | * 国际化对象方法,可以自定义使用方法返回 注意:如果改变国际化方法记得把该方法加到ignoreMethods忽略列表里面
41 | */
42 | i18nObjectMethod: 'i18n.t',
43 | /**
44 | * 国际化方法简写模式,可以自定使用方法返回 注意:如果改变国际化方法记得把该方法加到ignoreMethods忽略列表里面
45 | */
46 | i18nMethod: '$t',
47 | /**
48 | * 如果不喜欢又臭又长的key 可以自定义国际化配置文件的key
49 | * 默认为 false 不自定义
50 | */
51 | setMessageKey: false,
52 | /**
53 | * 生成md5的key长度 true: 32位字符 false: 16位字符
54 | */
55 | maxLenKey: false,
56 | /**
57 | * 国际化要注入到js里面的实例 会在js文件第一行注入
58 | */
59 | i18nInstance: "import i18n from '~/i18n'",
60 | /**
61 | * 格式化文件配置
62 | */
63 | prettier: {
64 | singleQuote: true,
65 | trailingComma: 'es5',
66 | endOfLine: 'lf',
67 | }
68 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | /logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # parcel-bundler cache (https://parceljs.org/)
61 | .cache
62 |
63 | # Nuxt generate
64 | dist
65 |
66 | # vuepress build output
67 | .vuepress/dist
68 |
69 | # Serverless directories
70 | .serverless
71 |
72 | # IDE / Editor
73 | .idea
74 |
75 | # Service worker
76 | sw.*
77 |
78 | # macOS
79 | .DS_Store
80 |
81 | # Vim swap files
82 | *.swp
83 |
84 | package-lock.json
85 |
86 | yarn-lock.json
87 |
88 | dist.zip
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "autoi18n-tool",
3 | "version": "1.0.8",
4 | "main": "./loaders/index.js",
5 | "license": "MIT",
6 | "bin": {
7 | "autoi18n": "./cli/bin/index.js"
8 | },
9 | "keywords": [
10 | "i18n",
11 | "autoi18n",
12 | "auto-i18n",
13 | "autoi18n-tool"
14 | ],
15 | "repository": {
16 | "url": "https://github.com/Gertyxs/autoi18n/tree/main"
17 | },
18 | "files": [
19 | "cli",
20 | "core",
21 | "loaders"
22 | ],
23 | "author": "Gertyxs ",
24 | "scripts": {},
25 | "dependencies": {
26 | "@babel/core": "^7.14.8",
27 | "@babel/generator": "^7.14.8",
28 | "@babel/plugin-proposal-optional-chaining": "^7.14.5",
29 | "@babel/plugin-syntax-async-generators": "^7.8.4",
30 | "@babel/plugin-syntax-class-properties": "^7.12.13",
31 | "@babel/plugin-syntax-decorators": "^7.14.5",
32 | "@babel/plugin-syntax-do-expressions": "^7.14.5",
33 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
34 | "@babel/plugin-syntax-export-extensions": "^7.0.0-beta.32",
35 | "@babel/plugin-syntax-function-bind": "^7.14.5",
36 | "@babel/plugin-syntax-jsx": "^7.14.5",
37 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
38 | "@babel/preset-typescript": "^7.14.5",
39 | "@babel/traverse": "^7.14.8",
40 | "@babel/types": "^7.14.8",
41 | "chalk": "^4.1.1",
42 | "commander": "^8.0.0",
43 | "crypto-js": "^4.1.1",
44 | "glob": "^7.1.7",
45 | "inquirer": "^8.1.1",
46 | "prettier": "^2.3.2"
47 | },
48 | "peerDependencies": {},
49 | "devDependencies": {}
50 | }
51 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--watchAll') === -1 &&
45 | argv.indexOf('--watchAll=false') === -1
46 | ) {
47 | // https://github.com/facebook/create-react-app/issues/5210
48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
49 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
50 | }
51 |
52 |
53 | jest.run(argv);
54 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--watchAll') === -1 &&
45 | argv.indexOf('--watchAll=false') === -1
46 | ) {
47 | // https://github.com/facebook/create-react-app/issues/5210
48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
49 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
50 | }
51 |
52 |
53 | jest.run(argv);
54 |
--------------------------------------------------------------------------------
/cli/command/restore.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const mergeIi8nConfig = require('../utils/mergeIi8nConfig')
3 | const prettier = require('prettier')
4 | const log = require('../utils/log')
5 | const baseUtils = require('../utils/baseUtils')
6 | const { restore } = require('../../core/index')
7 | const LocaleFile = require('../utils/localeFile')
8 |
9 | /**
10 | * 恢复国际化字段为对应文案
11 | * @param {*} programOption 命令行参数
12 | */
13 | module.exports = async function (programOption) {
14 | // 合并配置文件
15 | const options = mergeIi8nConfig(programOption)
16 | // 国际化配置文件路径
17 | const firstLocalePath = options.language && options.language[0] ? options.language[0] : 'zh-cn'
18 |
19 | // 没有指定国际化文件配置
20 | if (!firstLocalePath && !programOption.file) {
21 | log.error('Internationalization configuration file not found');
22 | process.exit(2);
23 | }
24 |
25 | // 创建生成国际化文件对象
26 | const localeFile = new LocaleFile(options.localePath)
27 |
28 | // 国际化配置数据
29 | let messages = {}
30 | if (programOption.file) {
31 | messages = localeFile.getConf(firstLocalePath, options, programOption.file)
32 | } else {
33 | messages = localeFile.getConf(firstLocalePath, options)
34 | }
35 |
36 | // 获取所有入口文件路劲
37 | let targetFiles = baseUtils.getSourceFiles(options)
38 | // 开始读取文件进行操作
39 | for (let i = 0; i < targetFiles.length; i++) {
40 | const sourceCode = fs.readFileSync(targetFiles[i].filePath, 'utf8');
41 | let code = restore({ code: sourceCode, targetFile: targetFiles[i], options, messages })
42 | code = prettier.format(code, baseUtils.getPrettierOptions(targetFiles[i].ext, options))
43 | fs.writeFileSync(targetFiles[i].filePath, code, { encoding: 'utf-8' })
44 | log.success(`done: ${targetFiles[i].filePath}`)
45 | }
46 | }
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cli/command/collect.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const mergeIi8nConfig = require('../utils/mergeIi8nConfig')
3 | const prettier = require('prettier')
4 | const log = require('../utils/log')
5 | const baseUtils = require('../utils/baseUtils')
6 | const { transform } = require('../../core/index')
7 | const LocaleFile = require('../utils/localeFile')
8 |
9 | /**
10 | * 同步国际化配置文件并替换为对应的国际化字段
11 | * @param {*} programOption 命令行参数
12 | */
13 | module.exports = async function (programOption) {
14 | // 合并配置文件
15 | const options = mergeIi8nConfig(programOption)
16 |
17 | // 指定目录类型错误
18 | if (!Array.isArray(options.entry) && typeof options.entry !== 'string') {
19 | log.error('entry must be a string or array');
20 | process.exit(2);
21 | }
22 |
23 | // 没有指定国际化目录
24 | if (!options.entry || Array.isArray(options.entry) && options.entry.length <= 0) {
25 | log.error('no entry is specified');
26 | process.exit(2);
27 | }
28 |
29 | // 国际化配置数据
30 | const messages = {}
31 |
32 | // 获取所有入口文件路劲
33 | let targetFiles = baseUtils.getSourceFiles(options)
34 | // 开始读取文件进行操作
35 | for (let i = 0; i < targetFiles.length; i++) {
36 | const sourceCode = fs.readFileSync(targetFiles[i].filePath, 'utf8');
37 | let code = transform({ code: sourceCode, targetFile: targetFiles[i], options, messages })
38 | if (programOption.replace) {
39 | code = prettier.format(code, baseUtils.getPrettierOptions(targetFiles[i].ext, options))
40 | fs.writeFileSync(targetFiles[i].filePath, code, { encoding: 'utf-8' })
41 | }
42 | log.success(`done: ${targetFiles[i].filePath}`)
43 | }
44 |
45 | // 创建生成国际化文件对象
46 | const localeFile = new LocaleFile(options.localePath)
47 | // 生成配置文件
48 | createTasks = options.language.map(locale => {
49 | let data = localeFile.getConf(locale, options)
50 | data = baseUtils.mergeMessages(data, messages)
51 | return localeFile.createConf(data, locale, options)
52 | })
53 | log.success('生成国际化配置文件完成')
54 |
55 | }
--------------------------------------------------------------------------------
/cli/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const program = require('commander')
3 | const initFileConf = require('./command/initFileConf')
4 | const collect = require('./command/collect')
5 | const package = require('../package')
6 | const restore = require('./command/restore')
7 |
8 | // 用法 版本说明
9 | program
10 | .version(package.version) // 定义版本
11 | .usage('') // 定义用法
12 |
13 | // 初始化配置文件
14 | program
15 | .command('init') // 定义命令
16 | .alias('i') // 命令别名
17 | .description('init locales conf') // 对命令参数的描述信息
18 | .action(function (options) {
19 | initFileConf(options)
20 | })
21 | .on('--help', function () {
22 | console.log(' Examples:')
23 | console.log(' $ autoi18n init')
24 | })
25 |
26 | // 同步国际化配置文件并替换为对应的国际化字段
27 | program
28 | .command('sync') // 定义命令
29 | .alias('s') // 命令别名
30 | .description('Synchronize the Chinese configuration to the internationalization profile') // 对命令参数的描述信息
31 | .option('-r, --replace', 'Replace Internationalization Fields') // 替换国际化字段 如果为true 会写入源文件 默认为false
32 | .option('-c, --config ', 'set config path. defaults to ./autoi18n.config.js') // 指定配置文件
33 | .action(function (options) {
34 | collect(options)
35 | })
36 | .on('--help', function () {
37 | console.log(' Examples:');
38 | console.log(' $ autoi18n sync')
39 | })
40 |
41 | // 恢复国际化字段为对应文案
42 | program
43 | .command('restore') // 定义命令
44 | .alias('r') // 命令别名
45 | .description('Restore the internationalized field to the corresponding text') // 对命令参数的描述信息
46 | .option('-c, --config ', 'set config path. defaults to ./autoi18n.config.js') // 指定配置文件
47 | .option('-f, --file ', 'Internationalization configuration file path') // 国际化配置文件路径 默认会获取 language的第一个文件配置数据
48 | .action(function (options) {
49 | restore(options)
50 | })
51 | .on('--help', function () {
52 | console.log(' Examples:')
53 | console.log(' $ autoi18n restore -f ./src/locales/zh-cn.ts')
54 | })
55 |
56 | program.parse(process.argv)
57 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/getHttpsConfig.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const crypto = require('crypto');
6 | const chalk = require('react-dev-utils/chalk');
7 | const paths = require('./paths');
8 |
9 | // Ensure the certificate and key provided are valid and if not
10 | // throw an easy to debug error
11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12 | let encrypted;
13 | try {
14 | // publicEncrypt will throw an error with an invalid cert
15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16 | } catch (err) {
17 | throw new Error(
18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19 | );
20 | }
21 |
22 | try {
23 | // privateDecrypt will throw an error with an invalid key
24 | crypto.privateDecrypt(key, encrypted);
25 | } catch (err) {
26 | throw new Error(
27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28 | err.message
29 | }`
30 | );
31 | }
32 | }
33 |
34 | // Read file and throw an error if it doesn't exist
35 | function readEnvFile(file, type) {
36 | if (!fs.existsSync(file)) {
37 | throw new Error(
38 | `You specified ${chalk.cyan(
39 | type
40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41 | );
42 | }
43 | return fs.readFileSync(file);
44 | }
45 |
46 | // Get the https config
47 | // Return cert files if provided in env, otherwise just true or false
48 | function getHttpsConfig() {
49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50 | const isHttps = HTTPS === 'true';
51 |
52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55 | const config = {
56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58 | };
59 |
60 | validateKeyAndCerts({ ...config, keyFile, crtFile });
61 | return config;
62 | }
63 | return isHttps;
64 | }
65 |
66 | module.exports = getHttpsConfig;
67 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/getHttpsConfig.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const crypto = require('crypto');
6 | const chalk = require('react-dev-utils/chalk');
7 | const paths = require('./paths');
8 |
9 | // Ensure the certificate and key provided are valid and if not
10 | // throw an easy to debug error
11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12 | let encrypted;
13 | try {
14 | // publicEncrypt will throw an error with an invalid cert
15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16 | } catch (err) {
17 | throw new Error(
18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19 | );
20 | }
21 |
22 | try {
23 | // privateDecrypt will throw an error with an invalid key
24 | crypto.privateDecrypt(key, encrypted);
25 | } catch (err) {
26 | throw new Error(
27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28 | err.message
29 | }`
30 | );
31 | }
32 | }
33 |
34 | // Read file and throw an error if it doesn't exist
35 | function readEnvFile(file, type) {
36 | if (!fs.existsSync(file)) {
37 | throw new Error(
38 | `You specified ${chalk.cyan(
39 | type
40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41 | );
42 | }
43 | return fs.readFileSync(file);
44 | }
45 |
46 | // Get the https config
47 | // Return cert files if provided in env, otherwise just true or false
48 | function getHttpsConfig() {
49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50 | const isHttps = HTTPS === 'true';
51 |
52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55 | const config = {
56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58 | };
59 |
60 | validateKeyAndCerts({ ...config, keyFile, crtFile });
61 | return config;
62 | }
63 | return isHttps;
64 | }
65 |
66 | module.exports = getHttpsConfig;
67 |
--------------------------------------------------------------------------------
/core/utils/cacheCommentJs.js:
--------------------------------------------------------------------------------
1 | const baseUtils = require('./baseUtils')
2 | /**
3 | * 对js注释缓存 恢复处理
4 | */
5 | module.exports = {
6 | /**
7 | * 暂存Js注释对象
8 | */
9 | commentsCache: {},
10 | /**
11 | * 先去掉js的注释 暂存注释
12 | */
13 | stash(sourceCode, options) {
14 | this.commentsCache = {}
15 | let commentsIndex = 0
16 | sourceCode = sourceCode.replace(/(\/(\/.*))|(\/\*([\s\S]*?)\*\/)/g, (match, comment1, content1, comment2, content2, offset, string) => {
17 | let comment = ''
18 | let content = ''
19 | // 单行注释 防止匹配到url协议部分 类似 http://
20 | if (comment1 != undefined && comment1 != null && comment1.length > 0) {
21 | const len = '//'.length
22 | if (offset == 0) {
23 | // 匹配字符串起始就是 //,所以整行都是注释
24 | comment = comment1
25 | content = content1
26 | }
27 | // 获取当前字符串中第一个纯正的单选注释 //
28 | let idxSlash = 0;
29 | while ((idxSlash = comment1.indexOf('//', idxSlash)) >= 0) {
30 | let prefix = string.charAt(offset + idxSlash - 1)
31 | if (prefix === ':') {
32 | // 前一个字符是':',所以不是单行注释
33 | idxSlash = idxSlash + len
34 | continue
35 | } else {
36 | // 拿出注释
37 | comment = comment1.substring(idxSlash, comment1.length)
38 | content = comment1.substring(idxSlash + len, comment1.length)
39 | break
40 | }
41 | }
42 | }
43 | // 多行注释
44 | if (comment2 !== undefined && comment2 !== null && comment2.length > 0) {
45 | comment = comment2
46 | content = content2
47 | }
48 | // 如果存在注释
49 | if (comment && content) {
50 | let commentsKey = `%%comment_js_${commentsIndex++}%%`
51 | this.commentsCache[commentsKey] = content
52 | return match.replace(content, commentsKey)
53 | } else {
54 | return match
55 | }
56 | })
57 | return sourceCode
58 | },
59 | /**
60 | * 恢复之前删除的注释
61 | */
62 | restore(sourceCode, options) {
63 | sourceCode = sourceCode.replace(/%%comment_js_\d+%%/gim, (match) => {
64 | return this.commentsCache[match]
65 | });
66 | this.commentsCache = {} // 清除缓存
67 | return sourceCode
68 | }
69 | }
--------------------------------------------------------------------------------
/core/transform/transform.js:
--------------------------------------------------------------------------------
1 | const { md5, formatWhitespace } = require('../utils/baseUtils')
2 |
3 | /**
4 | * 设置替换
5 | * @param {*} code
6 | */
7 | const replaceStatement = ({ value, options, messages, ext, codeType, sign = "'" }) => {
8 | // 去掉首尾空白字符,中间的连续空白字符替换成一个空格
9 | value = formatWhitespace(value)
10 | // 生成key
11 | let key = md5(value, options.maxLenKey)
12 | // 是否自定义key
13 | if (options.setMessageKey && typeof options.setMessageKey === 'function') {
14 | key = options.setMessageKey({ key, value })
15 | }
16 | messages[key] = value
17 | let i18nMethod = null
18 | // 类型为vue标签采用缩写国际化方法的形式
19 | if (codeType === 'vueTag') {
20 | i18nMethod = options.i18nMethod
21 | } else {
22 | // 其余情况使用对象的方法
23 | i18nMethod = options.i18nObjectMethod
24 | }
25 | // 如果是函数
26 | if (i18nMethod && typeof i18nMethod !== 'string') {
27 | return i18nMethod({ key, value, options, ext, sign })
28 | }
29 | return `${i18nMethod}(${sign}${key}${sign})`
30 | }
31 |
32 | /**
33 | * 匹配字符串模块
34 | * @param {*} code
35 | */
36 | const matchStringTpl = ({ code, options, messages, codeType, ext }) => {
37 | // 匹配存在中文的字符串模板内容
38 | code = code.replace(/(`)(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/g, (match, sign, value) => {
39 | // 匹配占位符外面的内容
40 | const outValues = value
41 | .replace('`', '')
42 | .replace(/(\${)([^}]+)(})/gm, ',,')
43 | .split(',,')
44 | .filter(item => item)
45 | outValues.forEach(item => {
46 | value = value.replace(item, value => {
47 | // 是否是中文
48 | if (/[\u4e00-\u9fa5]+/g.test(value)) {
49 | value = `\${'${value}'}`
50 | }
51 | return value
52 | })
53 | })
54 | return `${sign}${value}${sign}`
55 | })
56 | return code
57 | }
58 |
59 | /**
60 | * 匹配普通字符串
61 | * @param {*} code
62 | */
63 | const matchString = ({ code, options, messages, ext, codeType }) => {
64 | // 替换所有包含中文的普通字符串
65 | code = code.replace(/(['"])(((?!\1).)*[\u4e00-\u9fa5]+((?!\1).)*)\1/gm, (match, sign, value) => {
66 | return replaceStatement({ value, options, messages, ext, codeType, sign })
67 | })
68 | return code
69 | }
70 |
71 | module.exports = {
72 | matchStringTpl,
73 | matchString,
74 | replaceStatement
75 | }
76 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 |
7 | check out the
8 | vue-cli documentation
9 | .
10 |
11 |
Installed CLI Plugins
12 |
16 |
Essential Links
17 |
24 |
Ecosystem
25 |
32 |
33 |
34 |
35 |
43 |
44 |
60 |
--------------------------------------------------------------------------------
/examples/vue-autoi18n-cli/vue.config.js:
--------------------------------------------------------------------------------
1 |
2 | const isDev = process.env.DEPLOY_ENV === 'development' // 开发环境
3 | const isProd = process.env.DEPLOY_ENV === 'production' // 生产环境
4 | const isTest = process.env.DEPLOY_ENV === 'test' // 测试环境
5 | const isDeploy = isProd || isTest // 是否是部署环境
6 |
7 | module.exports = {
8 | parallel: false,
9 | productionSourceMap: isDev,
10 | lintOnSave: true,
11 | publicPath: isDeploy ? '' : '',
12 | assetsDir: 'static',
13 | css: {
14 | // 是否使用css分离插件 ExtractTextPlugin
15 | extract: isDeploy,
16 | // 开启 CSS source maps?
17 | sourceMap: false
18 | },
19 | chainWebpack: (config) => {
20 | if (isProd) {
21 | // 压缩去除console等信息
22 | config.optimization.minimizer('terser').tap((args) => {
23 | Object.assign(args[0].terserOptions.compress, {
24 | // warnings: false , // 默认 false
25 | // drop_console: false, // 默认
26 | // drop_debugger: true, // 去掉 debugger 默认也是true
27 | pure_funcs: ['console.log']
28 | })
29 | return args
30 | })
31 | }
32 | if (isTest) {
33 | config.optimization.minimizer('terser').tap((args) => {
34 | Object.assign(args[0].terserOptions.compress, {
35 | // warnings: false , // 默认 false
36 | // drop_console: false, // 默认
37 | // drop_debugger: true, // 去掉 debugger 默认也是true
38 | // pure_funcs: ['console.log']
39 | })
40 | return args
41 | })
42 | }
43 | },
44 | devServer: {
45 | open: true, //是否自动弹出浏览器页面
46 | port: 8089, // 设置端口号
47 | https: false, //是否使用https协议
48 | hotOnly: false, //是否开启热更新
49 | proxy: {
50 | // 互金API代理
51 | '/cashier/api': {
52 | // '/cashier-uat/api/': {
53 | // target: 'http://172.16.1.204:9001', // 水平电脑
54 | // target: 'http://172.16.1.103:9001', // 曼婷电脑
55 | // target: 'http://172.16.1.63:9001', // 智文电脑
56 | target: 'https://kwgmb-test.kwgproperty.com', // 测试环境
57 | ws: true, // 代理websockets
58 | changeOrigin: true // 是否跨域,虚拟的站点需要更管origin
59 | // pathRewrite: {
60 | // '/cashier/api': ''
61 | // }
62 | },
63 | // APP API 代理
64 | '/kwgdspappapi/v2': {
65 | target: 'https://kwgmb-test.kwgproperty.com', // 测试环境
66 | ws: true, // 代理websockets
67 | changeOrigin: true // 是否跨域,虚拟的站点需要更管origin
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebook/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
13 | // "public path" at which the app is served.
14 | // webpack needs to know it to put the right
131 | ```
132 |
133 | 在`vue`单文件组织中可以看出我们只要处理`template`以及`script`里面的代码即可
134 |
135 | 对于`vue`单文件组件我们可以使用官方提供的解析库[vue-template-compiler](https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme)提取`template`和`JavaScript`
136 |
137 | ```js
138 | const compiler = require('vue-template-compiler')
139 | const sfc = compiler.parseComponent(code, { pad: 'space', deindent: false })
140 | const { template, script, styles, customBlocks } = sfc
141 | ```
142 |
143 | 上面就把`vue`单文件组织分成了`template`和`JavaScript`了。
144 |
145 | **template**
146 |
147 | 对于`template`我们只需要处理标签的静态属性、动态属性和标签内容即可,处理这些字符我们可以使用[parse5](https://www.npmjs.com/package/parse5)去解析标签或者用`RegExp`匹配,在[autoi8n](https://github.com/Gertyxs/autoi18n)中我使用`RegExp`匹配,如有需要后期会采用标签解析器处理。
148 |
149 | ```js
150 | const matchTagAttr = ({ code }) => {
151 | code = code.replace(/(<[^\/\s]+)([^<>]+)(\/?>)/gm, (match, startTag, attrs, endTag) => {
152 | return code
153 | })
154 | return code
155 | }
156 | const matchTagContent = ({ code, options, ext, codeType, messages }) => {
157 | code = code.replace(/(>)([^><]*[\u4e00-\u9fa5]+[^><]*)(<)/gm, (match, beforeSign, value, afterSign) => {
158 | return code
159 | })
160 | return code
161 | }
162 | ```
163 |
164 | **JavaScript**
165 |
166 | 在上面我们已经讲解过`JavaScript`的处理了,在这里不再敖述。
167 |
168 | #### 3.3 React jsx中的字符
169 |
170 | ```jsx
171 | const render = () => {
172 | return (
173 |
174 |
175 | {'我是react内容'}
176 | 我是标签静态内容
177 |
178 |
179 | )
180 | }
181 | ```
182 |
183 | 在`react`中,我们同样处理`JavaScript`字符串,`jsx`标签属性和标签内容,由于`jsx`本身支持[bable ast](https://astexplorer.net/)解析所以我们直接使用`ast`进行解析。
184 |
185 | **代码分析完成**
186 |
187 | 通过上面对js、vue、JavaScript、的代码分析,我们已经匹配到所有需要做国际化的字符,我们再把对应的字符抽出生成一个messages对象写进文件就完成了资源文件的生成了。然后把对应的字符替换成我们国际化设置的方法就大功告成了。
188 |
189 | #### 3.4 webpack loader 实现无侵入式的自动国国际化
190 |
191 | 实现了对代码处理之后,还可以在webpack loader里面把源码传进来进行无侵入式处理
192 |
193 | ```js
194 | const path = require('path')
195 | const fs = require('fs')
196 | const mergeIi8nConfig = require('../cli/utils/mergeIi8nConfig');
197 | const { transform } = require('../core/index')
198 | let messages = {}
199 |
200 | module.exports = function (source) {
201 | const configOptions = mergeIi8nConfig()
202 | let targetFile = { ext: path.extname(this.resourcePath), filePath: this.resourcePath }
203 | source = transform({ code: source, targetFile, options: configOptions, messages })
204 | messages = {}
205 | return source
206 | }
207 | ```
208 |
209 | webpack loader 配置
210 |
211 | ```js
212 | {
213 | enforce: 'pre', // 此项一定要加上 优先执行的loader
214 | test: /\.(js|mjs|jsx|ts|tsx)$/,
215 | use: [
216 | {
217 | loader: 'autoi18n',
218 | options: {}
219 | }],
220 | exclude: /node_modules/
221 | }
222 | ```
223 |
224 |
225 |
226 | ## 总结
227 |
228 | 整篇文章主要围绕怎么实现项目国际化自动化,希望本文可以给你带来国际化自动化的思路,总的来说国际化自动化就是处理代码中的字符串,可以通过代码解析器或者正则去匹配对应的字符,然后抽取出来替换成对应的国际化key,感谢你的阅读。
229 |
230 |
231 |
232 | 分享一个国际化自动化的库[autoi18n](https://github.com/Gertyxs/autoi18n)
233 |
234 | 项目还在完善中,欢迎大家pr,如果你觉得不错也欢迎给个start 😄😄😄
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/config/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
5 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
6 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
7 | const ignoredFiles = require('react-dev-utils/ignoredFiles');
8 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
9 | const paths = require('./paths');
10 | const getHttpsConfig = require('./getHttpsConfig');
11 |
12 | const host = process.env.HOST || '0.0.0.0';
13 | const sockHost = process.env.WDS_SOCKET_HOST;
14 | const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node'
15 | const sockPort = process.env.WDS_SOCKET_PORT;
16 |
17 | module.exports = function (proxy, allowedHost) {
18 | return {
19 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
20 | // websites from potentially accessing local content through DNS rebinding:
21 | // https://github.com/webpack/webpack-dev-server/issues/887
22 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
23 | // However, it made several existing use cases such as development in cloud
24 | // environment or subdomains in development significantly more complicated:
25 | // https://github.com/facebook/create-react-app/issues/2271
26 | // https://github.com/facebook/create-react-app/issues/2233
27 | // While we're investigating better solutions, for now we will take a
28 | // compromise. Since our WDS configuration only serves files in the `public`
29 | // folder we won't consider accessing them a vulnerability. However, if you
30 | // use the `proxy` feature, it gets more dangerous because it can expose
31 | // remote code execution vulnerabilities in backends like Django and Rails.
32 | // So we will disable the host check normally, but enable it if you have
33 | // specified the `proxy` setting. Finally, we let you override it if you
34 | // really know what you're doing with a special environment variable.
35 | disableHostCheck:
36 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
37 | // Enable gzip compression of generated files.
38 | compress: true,
39 | // Silence WebpackDevServer's own logs since they're generally not useful.
40 | // It will still show compile warnings and errors with this setting.
41 | clientLogLevel: 'none',
42 | // By default WebpackDevServer serves physical files from current directory
43 | // in addition to all the virtual build products that it serves from memory.
44 | // This is confusing because those files won’t automatically be available in
45 | // production build folder unless we copy them. However, copying the whole
46 | // project directory is dangerous because we may expose sensitive files.
47 | // Instead, we establish a convention that only files in `public` directory
48 | // get served. Our build script will copy `public` into the `build` folder.
49 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
50 | //
51 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
52 | // Note that we only recommend to use `public` folder as an escape hatch
53 | // for files like `favicon.ico`, `manifest.json`, and libraries that are
54 | // for some reason broken when imported through webpack. If you just want to
55 | // use an image, put it in `src` and `import` it from JavaScript instead.
56 | contentBase: paths.appPublic,
57 | contentBasePublicPath: paths.publicUrlOrPath,
58 | // By default files from `contentBase` will not trigger a page reload.
59 | watchContentBase: true,
60 | // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint
61 | // for the WebpackDevServer client so it can learn when the files were
62 | // updated. The WebpackDevServer client is included as an entry point
63 | // in the webpack development configuration. Note that only changes
64 | // to CSS are currently hot reloaded. JS changes will refresh the browser.
65 | hot: true,
66 | // Use 'ws' instead of 'sockjs-node' on server since we're using native
67 | // websockets in `webpackHotDevClient`.
68 | transportMode: 'ws',
69 | // Prevent a WS client from getting injected as we're already including
70 | // `webpackHotDevClient`.
71 | injectClient: false,
72 | // Enable custom sockjs pathname for websocket connection to hot reloading server.
73 | // Enable custom sockjs hostname, pathname and port for websocket connection
74 | // to hot reloading server.
75 | sockHost,
76 | sockPath,
77 | sockPort,
78 | // It is important to tell WebpackDevServer to use the same "publicPath" path as
79 | // we specified in the webpack config. When homepage is '.', default to serving
80 | // from the root.
81 | // remove last slash so user can land on `/test` instead of `/test/`
82 | publicPath: paths.publicUrlOrPath.slice(0, -1),
83 | // WebpackDevServer is noisy by default so we emit custom message instead
84 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above.
85 | quiet: true,
86 | // Reportedly, this avoids CPU overload on some systems.
87 | // https://github.com/facebook/create-react-app/issues/293
88 | // src/node_modules is not ignored to support absolute imports
89 | // https://github.com/facebook/create-react-app/issues/1065
90 | watchOptions: {
91 | ignored: ignoredFiles(paths.appSrc),
92 | },
93 | https: getHttpsConfig(),
94 | host,
95 | overlay: false,
96 | historyApiFallback: {
97 | // Paths with dots should still use the history fallback.
98 | // See https://github.com/facebook/create-react-app/issues/387.
99 | disableDotRule: true,
100 | index: paths.publicUrlOrPath,
101 | },
102 | public: allowedHost,
103 | // `proxy` is run between `before` and `after` `webpack-dev-server` hooks
104 | proxy,
105 | before(app, server) {
106 | // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
107 | // middlewares before `redirectServedPath` otherwise will not have any effect
108 | // This lets us fetch source contents from webpack for the error overlay
109 | app.use(evalSourceMapMiddleware(server));
110 | // This lets us open files from the runtime error overlay.
111 | app.use(errorOverlayMiddleware());
112 |
113 | if (fs.existsSync(paths.proxySetup)) {
114 | // This registers user provided middleware for proxy reasons
115 | require(paths.proxySetup)(app);
116 | }
117 | },
118 | after(app) {
119 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match
120 | app.use(redirectServedPath(paths.publicUrlOrPath));
121 |
122 | // This service worker file is effectively a 'no-op' that will reset any
123 | // previous service worker registered for the same host:port combination.
124 | // We do this in development to avoid hitting the production cache if
125 | // it used the same host and port.
126 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
127 | app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
128 | },
129 | };
130 | };
131 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/config/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
5 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
6 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
7 | const ignoredFiles = require('react-dev-utils/ignoredFiles');
8 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
9 | const paths = require('./paths');
10 | const getHttpsConfig = require('./getHttpsConfig');
11 |
12 | const host = process.env.HOST || '0.0.0.0';
13 | const sockHost = process.env.WDS_SOCKET_HOST;
14 | const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node'
15 | const sockPort = process.env.WDS_SOCKET_PORT;
16 |
17 | module.exports = function (proxy, allowedHost) {
18 | return {
19 | // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
20 | // websites from potentially accessing local content through DNS rebinding:
21 | // https://github.com/webpack/webpack-dev-server/issues/887
22 | // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
23 | // However, it made several existing use cases such as development in cloud
24 | // environment or subdomains in development significantly more complicated:
25 | // https://github.com/facebook/create-react-app/issues/2271
26 | // https://github.com/facebook/create-react-app/issues/2233
27 | // While we're investigating better solutions, for now we will take a
28 | // compromise. Since our WDS configuration only serves files in the `public`
29 | // folder we won't consider accessing them a vulnerability. However, if you
30 | // use the `proxy` feature, it gets more dangerous because it can expose
31 | // remote code execution vulnerabilities in backends like Django and Rails.
32 | // So we will disable the host check normally, but enable it if you have
33 | // specified the `proxy` setting. Finally, we let you override it if you
34 | // really know what you're doing with a special environment variable.
35 | disableHostCheck:
36 | !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
37 | // Enable gzip compression of generated files.
38 | compress: true,
39 | // Silence WebpackDevServer's own logs since they're generally not useful.
40 | // It will still show compile warnings and errors with this setting.
41 | clientLogLevel: 'none',
42 | // By default WebpackDevServer serves physical files from current directory
43 | // in addition to all the virtual build products that it serves from memory.
44 | // This is confusing because those files won’t automatically be available in
45 | // production build folder unless we copy them. However, copying the whole
46 | // project directory is dangerous because we may expose sensitive files.
47 | // Instead, we establish a convention that only files in `public` directory
48 | // get served. Our build script will copy `public` into the `build` folder.
49 | // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
50 | //
51 | // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
52 | // Note that we only recommend to use `public` folder as an escape hatch
53 | // for files like `favicon.ico`, `manifest.json`, and libraries that are
54 | // for some reason broken when imported through webpack. If you just want to
55 | // use an image, put it in `src` and `import` it from JavaScript instead.
56 | contentBase: paths.appPublic,
57 | contentBasePublicPath: paths.publicUrlOrPath,
58 | // By default files from `contentBase` will not trigger a page reload.
59 | watchContentBase: true,
60 | // Enable hot reloading server. It will provide WDS_SOCKET_PATH endpoint
61 | // for the WebpackDevServer client so it can learn when the files were
62 | // updated. The WebpackDevServer client is included as an entry point
63 | // in the webpack development configuration. Note that only changes
64 | // to CSS are currently hot reloaded. JS changes will refresh the browser.
65 | hot: true,
66 | // Use 'ws' instead of 'sockjs-node' on server since we're using native
67 | // websockets in `webpackHotDevClient`.
68 | transportMode: 'ws',
69 | // Prevent a WS client from getting injected as we're already including
70 | // `webpackHotDevClient`.
71 | injectClient: false,
72 | // Enable custom sockjs pathname for websocket connection to hot reloading server.
73 | // Enable custom sockjs hostname, pathname and port for websocket connection
74 | // to hot reloading server.
75 | sockHost,
76 | sockPath,
77 | sockPort,
78 | // It is important to tell WebpackDevServer to use the same "publicPath" path as
79 | // we specified in the webpack config. When homepage is '.', default to serving
80 | // from the root.
81 | // remove last slash so user can land on `/test` instead of `/test/`
82 | publicPath: paths.publicUrlOrPath.slice(0, -1),
83 | // WebpackDevServer is noisy by default so we emit custom message instead
84 | // by listening to the compiler events with `compiler.hooks[...].tap` calls above.
85 | quiet: true,
86 | // Reportedly, this avoids CPU overload on some systems.
87 | // https://github.com/facebook/create-react-app/issues/293
88 | // src/node_modules is not ignored to support absolute imports
89 | // https://github.com/facebook/create-react-app/issues/1065
90 | watchOptions: {
91 | ignored: ignoredFiles(paths.appSrc),
92 | },
93 | https: getHttpsConfig(),
94 | host,
95 | overlay: false,
96 | historyApiFallback: {
97 | // Paths with dots should still use the history fallback.
98 | // See https://github.com/facebook/create-react-app/issues/387.
99 | disableDotRule: true,
100 | index: paths.publicUrlOrPath,
101 | },
102 | public: allowedHost,
103 | // `proxy` is run between `before` and `after` `webpack-dev-server` hooks
104 | proxy,
105 | before(app, server) {
106 | // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware`
107 | // middlewares before `redirectServedPath` otherwise will not have any effect
108 | // This lets us fetch source contents from webpack for the error overlay
109 | app.use(evalSourceMapMiddleware(server));
110 | // This lets us open files from the runtime error overlay.
111 | app.use(errorOverlayMiddleware());
112 |
113 | if (fs.existsSync(paths.proxySetup)) {
114 | // This registers user provided middleware for proxy reasons
115 | require(paths.proxySetup)(app);
116 | }
117 | },
118 | after(app) {
119 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match
120 | app.use(redirectServedPath(paths.publicUrlOrPath));
121 |
122 | // This service worker file is effectively a 'no-op' that will reset any
123 | // previous service worker registered for the same host:port combination.
124 | // We do this in development to avoid hitting the production cache if
125 | // it used the same host and port.
126 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
127 | app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
128 | },
129 | };
130 | };
131 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-cli/scripts/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'production';
5 | process.env.NODE_ENV = 'production';
6 |
7 | // Makes the script crash on unhandled rejections instead of silently
8 | // ignoring them. In the future, promise rejections that are not handled will
9 | // terminate the Node.js process with a non-zero exit code.
10 | process.on('unhandledRejection', err => {
11 | throw err;
12 | });
13 |
14 | // Ensure environment variables are read.
15 | require('../config/env');
16 |
17 |
18 | const path = require('path');
19 | const chalk = require('react-dev-utils/chalk');
20 | const fs = require('fs-extra');
21 | const bfj = require('bfj');
22 | const webpack = require('webpack');
23 | const configFactory = require('../config/webpack.config');
24 | const paths = require('../config/paths');
25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
29 | const printBuildError = require('react-dev-utils/printBuildError');
30 |
31 | const measureFileSizesBeforeBuild =
32 | FileSizeReporter.measureFileSizesBeforeBuild;
33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
34 | const useYarn = fs.existsSync(paths.yarnLockFile);
35 |
36 | // These sizes are pretty large. We'll warn for bundles exceeding them.
37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
39 |
40 | const isInteractive = process.stdout.isTTY;
41 |
42 | // Warn and crash if required files are missing
43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
44 | process.exit(1);
45 | }
46 |
47 | const argv = process.argv.slice(2);
48 | const writeStatsJson = argv.indexOf('--stats') !== -1;
49 |
50 | // Generate configuration
51 | const config = configFactory('production');
52 |
53 | // We require that you explicitly set browsers and do not fall back to
54 | // browserslist defaults.
55 | const { checkBrowsers } = require('react-dev-utils/browsersHelper');
56 | checkBrowsers(paths.appPath, isInteractive)
57 | .then(() => {
58 | // First, read the current file sizes in build directory.
59 | // This lets us display how much they changed later.
60 | return measureFileSizesBeforeBuild(paths.appBuild);
61 | })
62 | .then(previousFileSizes => {
63 | // Remove all content but keep the directory so that
64 | // if you're in it, you don't end up in Trash
65 | fs.emptyDirSync(paths.appBuild);
66 | // Merge with the public folder
67 | copyPublicFolder();
68 | // Start the webpack build
69 | return build(previousFileSizes);
70 | })
71 | .then(
72 | ({ stats, previousFileSizes, warnings }) => {
73 | if (warnings.length) {
74 | console.log(chalk.yellow('Compiled with warnings.\n'));
75 | console.log(warnings.join('\n\n'));
76 | console.log(
77 | '\nSearch for the ' +
78 | chalk.underline(chalk.yellow('keywords')) +
79 | ' to learn more about each warning.'
80 | );
81 | console.log(
82 | 'To ignore, add ' +
83 | chalk.cyan('// eslint-disable-next-line') +
84 | ' to the line before.\n'
85 | );
86 | } else {
87 | console.log(chalk.green('Compiled successfully.\n'));
88 | }
89 |
90 | console.log('File sizes after gzip:\n');
91 | printFileSizesAfterBuild(
92 | stats,
93 | previousFileSizes,
94 | paths.appBuild,
95 | WARN_AFTER_BUNDLE_GZIP_SIZE,
96 | WARN_AFTER_CHUNK_GZIP_SIZE
97 | );
98 | console.log();
99 |
100 | const appPackage = require(paths.appPackageJson);
101 | const publicUrl = paths.publicUrlOrPath;
102 | const publicPath = config.output.publicPath;
103 | const buildFolder = path.relative(process.cwd(), paths.appBuild);
104 | printHostingInstructions(
105 | appPackage,
106 | publicUrl,
107 | publicPath,
108 | buildFolder,
109 | useYarn
110 | );
111 | },
112 | err => {
113 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
114 | if (tscCompileOnError) {
115 | console.log(
116 | chalk.yellow(
117 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n'
118 | )
119 | );
120 | printBuildError(err);
121 | } else {
122 | console.log(chalk.red('Failed to compile.\n'));
123 | printBuildError(err);
124 | process.exit(1);
125 | }
126 | }
127 | )
128 | .catch(err => {
129 | if (err && err.message) {
130 | console.log(err.message);
131 | }
132 | process.exit(1);
133 | });
134 |
135 | // Create the production build and print the deployment instructions.
136 | function build(previousFileSizes) {
137 | console.log('Creating an optimized production build...');
138 |
139 | const compiler = webpack(config);
140 | return new Promise((resolve, reject) => {
141 | compiler.run((err, stats) => {
142 | let messages;
143 | if (err) {
144 | if (!err.message) {
145 | return reject(err);
146 | }
147 |
148 | let errMessage = err.message;
149 |
150 | // Add additional information for postcss errors
151 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
152 | errMessage +=
153 | '\nCompileError: Begins at CSS selector ' +
154 | err['postcssNode'].selector;
155 | }
156 |
157 | messages = formatWebpackMessages({
158 | errors: [errMessage],
159 | warnings: [],
160 | });
161 | } else {
162 | messages = formatWebpackMessages(
163 | stats.toJson({ all: false, warnings: true, errors: true })
164 | );
165 | }
166 | if (messages.errors.length) {
167 | // Only keep the first error. Others are often indicative
168 | // of the same problem, but confuse the reader with noise.
169 | if (messages.errors.length > 1) {
170 | messages.errors.length = 1;
171 | }
172 | return reject(new Error(messages.errors.join('\n\n')));
173 | }
174 | if (
175 | process.env.CI &&
176 | (typeof process.env.CI !== 'string' ||
177 | process.env.CI.toLowerCase() !== 'false') &&
178 | messages.warnings.length
179 | ) {
180 | console.log(
181 | chalk.yellow(
182 | '\nTreating warnings as errors because process.env.CI = true.\n' +
183 | 'Most CI servers set it automatically.\n'
184 | )
185 | );
186 | return reject(new Error(messages.warnings.join('\n\n')));
187 | }
188 |
189 | const resolveArgs = {
190 | stats,
191 | previousFileSizes,
192 | warnings: messages.warnings,
193 | };
194 |
195 | if (writeStatsJson) {
196 | return bfj
197 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson())
198 | .then(() => resolve(resolveArgs))
199 | .catch(error => reject(new Error(error)));
200 | }
201 |
202 | return resolve(resolveArgs);
203 | });
204 | });
205 | }
206 |
207 | function copyPublicFolder() {
208 | fs.copySync(paths.appPublic, paths.appBuild, {
209 | dereference: true,
210 | filter: file => file !== paths.appHtml,
211 | });
212 | }
213 |
--------------------------------------------------------------------------------
/examples/react-autoi18n-loaders/scripts/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'production';
5 | process.env.NODE_ENV = 'production';
6 |
7 | // Makes the script crash on unhandled rejections instead of silently
8 | // ignoring them. In the future, promise rejections that are not handled will
9 | // terminate the Node.js process with a non-zero exit code.
10 | process.on('unhandledRejection', err => {
11 | throw err;
12 | });
13 |
14 | // Ensure environment variables are read.
15 | require('../config/env');
16 |
17 |
18 | const path = require('path');
19 | const chalk = require('react-dev-utils/chalk');
20 | const fs = require('fs-extra');
21 | const bfj = require('bfj');
22 | const webpack = require('webpack');
23 | const configFactory = require('../config/webpack.config');
24 | const paths = require('../config/paths');
25 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
26 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
27 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
28 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
29 | const printBuildError = require('react-dev-utils/printBuildError');
30 |
31 | const measureFileSizesBeforeBuild =
32 | FileSizeReporter.measureFileSizesBeforeBuild;
33 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
34 | const useYarn = fs.existsSync(paths.yarnLockFile);
35 |
36 | // These sizes are pretty large. We'll warn for bundles exceeding them.
37 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
38 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
39 |
40 | const isInteractive = process.stdout.isTTY;
41 |
42 | // Warn and crash if required files are missing
43 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
44 | process.exit(1);
45 | }
46 |
47 | const argv = process.argv.slice(2);
48 | const writeStatsJson = argv.indexOf('--stats') !== -1;
49 |
50 | // Generate configuration
51 | const config = configFactory('production');
52 |
53 | // We require that you explicitly set browsers and do not fall back to
54 | // browserslist defaults.
55 | const { checkBrowsers } = require('react-dev-utils/browsersHelper');
56 | checkBrowsers(paths.appPath, isInteractive)
57 | .then(() => {
58 | // First, read the current file sizes in build directory.
59 | // This lets us display how much they changed later.
60 | return measureFileSizesBeforeBuild(paths.appBuild);
61 | })
62 | .then(previousFileSizes => {
63 | // Remove all content but keep the directory so that
64 | // if you're in it, you don't end up in Trash
65 | fs.emptyDirSync(paths.appBuild);
66 | // Merge with the public folder
67 | copyPublicFolder();
68 | // Start the webpack build
69 | return build(previousFileSizes);
70 | })
71 | .then(
72 | ({ stats, previousFileSizes, warnings }) => {
73 | if (warnings.length) {
74 | console.log(chalk.yellow('Compiled with warnings.\n'));
75 | console.log(warnings.join('\n\n'));
76 | console.log(
77 | '\nSearch for the ' +
78 | chalk.underline(chalk.yellow('keywords')) +
79 | ' to learn more about each warning.'
80 | );
81 | console.log(
82 | 'To ignore, add ' +
83 | chalk.cyan('// eslint-disable-next-line') +
84 | ' to the line before.\n'
85 | );
86 | } else {
87 | console.log(chalk.green('Compiled successfully.\n'));
88 | }
89 |
90 | console.log('File sizes after gzip:\n');
91 | printFileSizesAfterBuild(
92 | stats,
93 | previousFileSizes,
94 | paths.appBuild,
95 | WARN_AFTER_BUNDLE_GZIP_SIZE,
96 | WARN_AFTER_CHUNK_GZIP_SIZE
97 | );
98 | console.log();
99 |
100 | const appPackage = require(paths.appPackageJson);
101 | const publicUrl = paths.publicUrlOrPath;
102 | const publicPath = config.output.publicPath;
103 | const buildFolder = path.relative(process.cwd(), paths.appBuild);
104 | printHostingInstructions(
105 | appPackage,
106 | publicUrl,
107 | publicPath,
108 | buildFolder,
109 | useYarn
110 | );
111 | },
112 | err => {
113 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
114 | if (tscCompileOnError) {
115 | console.log(
116 | chalk.yellow(
117 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n'
118 | )
119 | );
120 | printBuildError(err);
121 | } else {
122 | console.log(chalk.red('Failed to compile.\n'));
123 | printBuildError(err);
124 | process.exit(1);
125 | }
126 | }
127 | )
128 | .catch(err => {
129 | if (err && err.message) {
130 | console.log(err.message);
131 | }
132 | process.exit(1);
133 | });
134 |
135 | // Create the production build and print the deployment instructions.
136 | function build(previousFileSizes) {
137 | console.log('Creating an optimized production build...');
138 |
139 | const compiler = webpack(config);
140 | return new Promise((resolve, reject) => {
141 | compiler.run((err, stats) => {
142 | let messages;
143 | if (err) {
144 | if (!err.message) {
145 | return reject(err);
146 | }
147 |
148 | let errMessage = err.message;
149 |
150 | // Add additional information for postcss errors
151 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
152 | errMessage +=
153 | '\nCompileError: Begins at CSS selector ' +
154 | err['postcssNode'].selector;
155 | }
156 |
157 | messages = formatWebpackMessages({
158 | errors: [errMessage],
159 | warnings: [],
160 | });
161 | } else {
162 | messages = formatWebpackMessages(
163 | stats.toJson({ all: false, warnings: true, errors: true })
164 | );
165 | }
166 | if (messages.errors.length) {
167 | // Only keep the first error. Others are often indicative
168 | // of the same problem, but confuse the reader with noise.
169 | if (messages.errors.length > 1) {
170 | messages.errors.length = 1;
171 | }
172 | return reject(new Error(messages.errors.join('\n\n')));
173 | }
174 | if (
175 | process.env.CI &&
176 | (typeof process.env.CI !== 'string' ||
177 | process.env.CI.toLowerCase() !== 'false') &&
178 | messages.warnings.length
179 | ) {
180 | console.log(
181 | chalk.yellow(
182 | '\nTreating warnings as errors because process.env.CI = true.\n' +
183 | 'Most CI servers set it automatically.\n'
184 | )
185 | );
186 | return reject(new Error(messages.warnings.join('\n\n')));
187 | }
188 |
189 | const resolveArgs = {
190 | stats,
191 | previousFileSizes,
192 | warnings: messages.warnings,
193 | };
194 |
195 | if (writeStatsJson) {
196 | return bfj
197 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson())
198 | .then(() => resolve(resolveArgs))
199 | .catch(error => reject(new Error(error)));
200 | }
201 |
202 | return resolve(resolveArgs);
203 | });
204 | });
205 | }
206 |
207 | function copyPublicFolder() {
208 | fs.copySync(paths.appPublic, paths.appBuild, {
209 | dereference: true,
210 | filter: file => file !== paths.appHtml,
211 | });
212 | }
213 |
--------------------------------------------------------------------------------