├── src
├── chrome
│ ├── inject.js
│ ├── background
│ │ ├── index.js
│ │ └── hmr.js
│ └── manifest.json
├── assets
│ ├── logo.png
│ └── img
│ │ └── bt.svg
├── config.js
├── content
│ ├── components
│ │ ├── 0aN8S7.png
│ │ ├── lang-inspector
│ │ │ ├── config.js
│ │ │ ├── printer.js
│ │ │ ├── langMap.js
│ │ │ └── index.js
│ │ ├── check.js
│ │ ├── sheetToCode.js
│ │ └── App.vue
│ └── index.js
├── mixins
│ └── store.js
├── popup
│ ├── index.js
│ ├── index.html
│ └── components
│ │ └── App.vue
├── store.js
├── scripts
│ └── crx.js
└── utils
│ └── insert.js
├── .browserslistrc
├── babel.config.js
├── .gitattributes
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── .gitignore
├── .eslintrc.js
├── README.md
├── LICENSE
├── package.json
└── vue.config.js
/src/chrome/inject.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/app"]
3 | };
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rhan2020/vue-chrome-extension/master/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rhan2020/vue-chrome-extension/master/src/assets/logo.png
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | /**
3 | * 基地址
4 | */
5 | baseUrl: "https://*" // 线上
6 | };
7 |
--------------------------------------------------------------------------------
/src/chrome/background/index.js:
--------------------------------------------------------------------------------
1 | import installReload from "./hmr";
2 |
3 | // 安装热刷新功能
4 | installReload();
5 |
--------------------------------------------------------------------------------
/src/content/components/0aN8S7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rhan2020/vue-chrome-extension/master/src/content/components/0aN8S7.png
--------------------------------------------------------------------------------
/src/mixins/store.js:
--------------------------------------------------------------------------------
1 | import store from "@/store";
2 |
3 | export default {
4 | beforeCreate() {
5 | this.$store = store;
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/src/popup/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./components/App.vue";
3 |
4 | new Vue({
5 | el: "#app",
6 | render: h => h(App)
7 | });
8 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 |
4 | Vue.use(Vuex);
5 |
6 | export default new Vuex.Store({
7 | state: {},
8 | mutations: {},
9 | actions: {}
10 | });
11 |
--------------------------------------------------------------------------------
/src/content/components/lang-inspector/config.js:
--------------------------------------------------------------------------------
1 | // lilejia@bigo.sg
2 |
3 | module.exports = {
4 | // UI以英文为准;
5 | uiLang: "en",
6 | // 短文本比较,标志过长短标题,前端处理短文本时可能没考虑多语言溢出;
7 | shortTextCompare: {
8 | which: [10, 20], // 短文本长度范围
9 | scale: 1.8 // 超过2的字符长度就提示
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 | *.crx
23 | *.pem
24 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | globals: {
4 | chrome: true,
5 | },
6 | env: {
7 | node: true
8 | },
9 | extends: ["plugin:vue/essential", "@vue/prettier"],
10 | rules: {
11 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
12 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
13 | },
14 | parserOptions: {
15 | parser: "babel-eslint"
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-chrome-extension
2 |
3 | ## 模块安装
4 | ```
5 | npm install
6 | ```
7 |
8 | ### 运行调试
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### 打包编译
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### 打包编译crx文件
19 | ```
20 | npm run build:crx
21 | ```
22 |
23 | ### 调试插件
24 | - 使用`npm run serve`运行项目
25 | - 打开谷歌浏览器拓展程序页面
26 | - 打开开发者模式,选择`加载已解压的扩展程序`
27 | - 导入项目中的`dist`文件夹即可到目标页查看插件状态
28 |
29 | ### Customize configuration
30 | See [Configuration Reference](https://cli.vuejs.org/config/).
31 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-chrome-extension
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/popup/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-chrome-extension
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/content/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./components/App.vue";
3 | import insert from "@/utils/insert";
4 | import stroe from "@/mixins/store";
5 |
6 | // 注入js到页面
7 | injectJS();
8 |
9 | Vue.mixin(stroe);
10 |
11 | // 插入组件到页面中
12 | insert(App);
13 |
14 | function injectJS() {
15 | document.addEventListener("readystatechange", () => {
16 | const injectPath = "inject.js";
17 | const temp = document.createElement("script");
18 |
19 | temp.setAttribute("type", "text/javascript");
20 | // 获得的地址类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
21 | temp.src = chrome.extension.getURL(injectPath);
22 | document.body.appendChild(temp);
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/scripts/crx.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 | const manifest = require(path.resolve(__dirname, "../chrome/manifest.json"));
4 | const ChromeExtension = require("crx");
5 | const crxName = `${manifest.name}-v${manifest.version}.crx`;
6 | const crx = new ChromeExtension({
7 | privateKey: fs.readFileSync(path.resolve(__dirname, "../../dist.pem"))
8 | });
9 |
10 | crx
11 | .load(path.resolve(__dirname, "../../dist"))
12 | .then(crx => crx.pack())
13 | .then(crxBuffer => {
14 | fs.writeFile(crxName, crxBuffer, err =>
15 | err
16 | ? console.error(err)
17 | : console.log(`>>>>>>> ${crxName} <<<<<<< 已打包完成`)
18 | );
19 | })
20 | .catch(err => {
21 | console.error(err);
22 | });
23 |
--------------------------------------------------------------------------------
/src/utils/insert.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 |
3 | /**
4 | * 将vue组件插入到页面上
5 | * @param {object} component 组件
6 | * @param {string} insertSelector 插入选择器
7 | */
8 | function insert(component, insertSelector = "body") {
9 | insertDomFactory(component, insertSelector);
10 | }
11 |
12 | // 根据组件生成vue实例
13 | // 生成插入的dom
14 | function insertDomFactory(component, insertSelector) {
15 | const vm = generateVueInstance(component);
16 |
17 | generateInsertDom(insertSelector, vm);
18 | }
19 |
20 | // 遍历待插入的dom
21 | // 插入新创建的元素
22 | // 将vue实例挂载到新创建的元素上
23 | function generateInsertDom(insertSelector, vm) {
24 | const dom = document.querySelectorAll(insertSelector);
25 | dom.forEach(item => {
26 | const insert = document.createElement("div");
27 | insert.id = "insert-item";
28 | item.appendChild(insert);
29 | vm.$mount("#insert-item");
30 | });
31 | }
32 |
33 | function generateVueInstance(component) {
34 | const insertCon = Vue.extend(component);
35 |
36 | return new insertCon();
37 | }
38 |
39 | export default insert;
40 |
--------------------------------------------------------------------------------
/src/assets/img/bt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "chrome-sheetToCode",
4 | "description": "transform sheet to code",
5 | "version": "1.0.0",
6 | "browser_action": {
7 | "default_title": "chrome-sheetToCode",
8 | "default_icon": "assets/logo.png",
9 | "default_popup": "popup.html"
10 | },
11 | "permissions": [
12 | "webRequestBlocking",
13 | "notifications",
14 | "tabs",
15 | "webRequest",
16 | "http://*/",
17 | "https://*/",
18 | "",
19 | "storage",
20 | "activeTab"
21 | ],
22 | "background": {
23 | "scripts": ["js/background.js"]
24 | },
25 | "icons": {
26 | "16": "assets/logo.png",
27 | "48": "assets/logo.png",
28 | "128": "assets/logo.png"
29 | },
30 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
31 | "content_scripts": [
32 | {
33 | "matches": [
34 | "https://docs.google.com/*"
35 | ],
36 | "css": [
37 | "css/content.css"
38 | ],
39 | "js": [
40 | "js/content.js"
41 | ],
42 | "run_at": "document_end"
43 | }
44 | ],
45 | "web_accessible_resources": ["fonts/*", "inject.js"]
46 | }
--------------------------------------------------------------------------------
/src/popup/components/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
40 |
41 |
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jacano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-chrome-extension",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service build --watch",
7 | "crx": "node ./src/scripts/crx.js",
8 | "build:crx": "npm run build && node src/scripts/crx.js",
9 | "build": "vue-cli-service build",
10 | "build:zip": "vue-cli-service build --zip",
11 | "lint": "vue-cli-service lint"
12 | },
13 | "dependencies": {
14 | "axios": "^0.21.1",
15 | "core-js": "^2.6.5",
16 | "vue": "^2.6.10",
17 | "papaparse": "^5.1.0",
18 | "vuex": "^3.0.1"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "^3.12.0",
22 | "@vue/cli-plugin-eslint": "^3.12.0",
23 | "@vue/cli-service": "^3.12.0",
24 | "@vue/eslint-config-prettier": "^5.0.0",
25 | "babel-eslint": "^10.0.1",
26 | "copy-webpack-plugin": "^6.0.2",
27 | "crx": "^5.0.1",
28 | "eslint": "^5.16.0",
29 | "eslint-plugin-prettier": "^3.1.0",
30 | "eslint-plugin-vue": "^5.0.0",
31 | "jszip": "^3.7.1",
32 | "lint-staged": "^8.1.5",
33 | "node-sass": "^4.12.0",
34 | "prettier": "^1.18.2",
35 | "sass-loader": "^8.0.0",
36 | "vue-template-compiler": "^2.6.10",
37 | "zip-webpack-plugin": "^3.0.0"
38 | },
39 | "gitHooks": {
40 | "pre-commit": "lint-staged"
41 | },
42 | "lint-staged": {
43 | "*.{js,vue}": [
44 | "vue-cli-service lint",
45 | "git add"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/content/components/lang-inspector/printer.js:
--------------------------------------------------------------------------------
1 | // lilejia@bigo.sg
2 |
3 | require("console.table");
4 | // 字色编号:30黑,31红,32绿,33黄,34蓝,35紫,36深绿,37白色
5 | // 背景编号:40黑,41红,42绿,43黄,44蓝,45紫,46深绿,47白色
6 | const chalk = {
7 | red(str) {
8 | return `\x1b[31m${str}\x1b[0m`;
9 | },
10 | green(str) {
11 | return `\x1b[32m${str}\x1b[0m`;
12 | },
13 | yellow(str) {
14 | return `\x1b[33m${str}\x1b[0m`;
15 | },
16 | cyan(str) {
17 | return `\x1b[35m${str}\x1b[0m`;
18 | }
19 | };
20 |
21 | module.exports = {
22 | printReport(report) {
23 | const { reportList, warnList, status } = report;
24 | reportList.forEach(it => {
25 | console.log(chalk.green(`【多语言】:${it.msg}\n`));
26 | });
27 | if (status) {
28 | console.log(chalk.green("【多语言】没有发现文档的任何错误!\n"));
29 | return;
30 | }
31 | console.log(chalk.green("【多语言】发现问题:\n"));
32 | const errorList = (warnList || [])
33 | .filter(it => it.level === "error")
34 | .map(it => ({
35 | Type: chalk.red("Error"),
36 | Pos: chalk.yellow(it.pos),
37 | message: chalk.red(it.msg)
38 | }));
39 | const warnsList = (warnList || [])
40 | .filter(it => it.level === "warn")
41 | .map(it => ({
42 | Type: chalk.green("Warn"),
43 | Pos: chalk.yellow(it.pos),
44 | message: chalk.cyan(it.msg)
45 | }));
46 | console.table([...errorList, ...warnsList]);
47 | },
48 | printSheetList(sheets, choose) {
49 | sheets = sheets || [];
50 | sheets[choose] = chalk.yellow(`${sheets[choose]} (选中)`);
51 | console.log(
52 | chalk.green("【多语言】当前链接检测到[") +
53 | chalk.yellow(sheets.length) +
54 | chalk.green("]张表:\n")
55 | );
56 | console.log(chalk.green(" [ ") + sheets.join("、") + chalk.green(" ]\n"));
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/chrome/background/hmr.js:
--------------------------------------------------------------------------------
1 | // 加载文件
2 |
3 | const filesInDirectory = dir =>
4 | new Promise(resolve =>
5 | dir.createReader().readEntries(entries => {
6 | Promise.all(
7 | entries
8 | .filter(e => e.name[0] !== ".")
9 | .map(e =>
10 | e.isDirectory
11 | ? filesInDirectory(e)
12 | : new Promise(resolve => e.file(resolve))
13 | )
14 | )
15 | .then(files => [].concat(...files))
16 | .then(resolve);
17 | })
18 | );
19 |
20 | // 遍历插件目录,读取文件信息,组合文件名称和修改时间成数据
21 | const timestampForFilesInDirectory = dir =>
22 | filesInDirectory(dir).then(files =>
23 | files.map(f => f.name + f.lastModifiedDate).join()
24 | );
25 |
26 | // 刷新当前活动页
27 | const reload = () => {
28 | window.chrome.tabs.query(
29 | {
30 | active: true,
31 | currentWindow: true
32 | },
33 | tabs => {
34 | // NB: see https://github.com/xpl/crx-hotreload/issues/5
35 | if (tabs[0]) {
36 | window.chrome.tabs.reload(tabs[0].id);
37 | }
38 | // 强制刷新页面
39 | window.chrome.runtime.reload();
40 | }
41 | );
42 | };
43 |
44 | // 观察文件改动
45 | const watchChanges = (dir, lastTimestamp) => {
46 | timestampForFilesInDirectory(dir).then(timestamp => {
47 | // 文件没有改动则循环监听watchChanges方法
48 | if (!lastTimestamp || lastTimestamp === timestamp) {
49 | setTimeout(() => watchChanges(dir, timestamp), 1000); // retry after 1s
50 | } else {
51 | // 强制刷新页面
52 | reload();
53 | }
54 | });
55 | };
56 |
57 | const installReload = () => {
58 | window.chrome.management.getSelf(self => {
59 | if (self.installType === "development") {
60 | // 获取插件目录,监听文件变化
61 | window.chrome.runtime.getPackageDirectoryEntry(dir => watchChanges(dir));
62 | }
63 | });
64 | };
65 |
66 | export default installReload;
67 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const CopyWebpackPlugin = require("copy-webpack-plugin");
2 | const ZipWebpackPlugin = require("zip-webpack-plugin");
3 | const path = require("path");
4 |
5 | // 只需要复制的文件
6 | const copyFiles = [
7 | {
8 | from: path.resolve("src/chrome/manifest.json"),
9 | to: `${path.resolve("dist")}/manifest.json`
10 | },
11 | {
12 | from: path.resolve("src/assets"),
13 | to: path.resolve("dist/assets")
14 | },
15 | {
16 | from: path.resolve("src/chrome/inject.js"),
17 | to: path.resolve("dist")
18 | }
19 | ];
20 |
21 | const plugins = [
22 | new CopyWebpackPlugin({
23 | patterns: copyFiles
24 | })
25 | ];
26 | // 生产环境打包dist为zip
27 | if (process.argv.includes("--zip")) {
28 | plugins.push(
29 | new ZipWebpackPlugin({
30 | path: path.resolve("./"),
31 | filename: "dist.zip"
32 | })
33 | );
34 | }
35 |
36 | // 配置页面
37 | const pages = {};
38 | /**
39 | * popup 和 devtool 都需要html文件
40 | * 因此 chromeName 还可以添加devtool
41 | */
42 | const chromeName = ["popup"];
43 |
44 | chromeName.forEach(name => {
45 | pages[name] = {
46 | entry: `src/${name}/index.js`,
47 | template: `src/${name}/index.html`,
48 | filename: `${name}.html`
49 | };
50 | });
51 |
52 | module.exports = {
53 | pages,
54 | // 生产环境是否生成 sourceMap 文件
55 | productionSourceMap: false,
56 |
57 | configureWebpack: {
58 | // 多入口打包
59 | entry: {
60 | content: "./src/content/index.js",
61 | background: "./src/chrome/background/index.js"
62 | },
63 | output: {
64 | filename: "js/[name].js"
65 | },
66 | plugins
67 | },
68 | css: {
69 | extract: {
70 | filename: "css/[name].css"
71 | }
72 | },
73 |
74 | chainWebpack: config => {
75 | config.resolve.alias.set("@", path.resolve("src"));
76 | // 处理字体文件名,去除hash值
77 | const fontsRule = config.module.rule("fonts");
78 |
79 | // 清除已有的所有 loader。
80 | // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。
81 | fontsRule.uses.clear();
82 | fontsRule
83 | .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
84 | .use("url")
85 | .loader("url-loader")
86 | .options({
87 | limit: 1000,
88 | name: "fonts/[name].[ext]"
89 | });
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/src/content/components/lang-inspector/langMap.js:
--------------------------------------------------------------------------------
1 | // lilejia@bigo.sg
2 |
3 | module.exports = {
4 | cn: "cn,zh,zh-hans,zh-cn,zh-hans-cn,zh-sg,zh-hans-sg",
5 | // 简体中文
6 | tw: "tw,zh-hant,zh-hk,zh-mo,zh-tw,zh-hant-hk,zh-hant-mo,zh-hant-tw",
7 | // 繁体中文
8 | en:
9 | "en,en-au,en-bz,en-ca,en-cb,en-ie,en-jm,en-nz,en-ph,en-za,en-tt,en-gb,en-us,en-zw,en-sg",
10 | // 英语
11 | th: "th,th-th",
12 | // 傣语
13 | vi: "vn,vi-vn,vi,vn-vn",
14 | // 越南语
15 | ru: "ru,ru-ru,ru-mo",
16 | // 俄语
17 | id: "id,id-id,in-id",
18 | // 印尼语
19 | ko: "ko,ko-kr",
20 | // 朝鲜语
21 | hi: "in,hi,hi-in",
22 | // 印地语
23 | ar:
24 | "ar,ar-er,ar-sa,ar-eg,ar-dz,ar-tn,ar-ye,ar-jo,ar-kw,ar-bh,ar-iq,ar-ly,ar-ma,ar-om,ar-sy,ar-lb,ar-ae,ar-qa,ar-ss,ar-il",
25 | // 阿拉伯语
26 | af: "af,af-za",
27 | // 南非语
28 | tr: "tr,tr-tr",
29 | // 土耳其语
30 | es:
31 | "es,es-ar,es-bo,es-cl,es-co,es-cr,es-do,es-ec,es-es,es-gt,es-hn,es-mx,es-ni,es-pa,es-pe,es-pr,es-py,es-sv,es-uy,es-ve,es-xl",
32 | // 西班牙语
33 | ms: "ms,ms-bn,ms-my,my",
34 | // 马来语
35 | pt: "pt,pt-pt,pt-br",
36 | // 葡萄牙语
37 | ja: "ja,ja-jp,ja-ja,jp,jp-jp",
38 | // 日语
39 | ur: "ur,ur-pk",
40 | // 乌尔都语
41 | de: "de,de-at,de-ch,de-de,de-li,de-lu",
42 | // 德语
43 | ne: "ne,ne-np",
44 | // 尼泊尔语
45 | bn: "bn,bn-bd,bn-in",
46 | // 孟加拉语
47 | gu: "gu,gu-in",
48 | // 古吉拉特语
49 | kn: "kn,kn-in",
50 | // 埃纳德语
51 | mr: "mr,mr-in",
52 | // 马拉地语
53 | pa: "pa,pa-in",
54 | // 旁遮普语
55 | ta: "ta,ta-in",
56 | // 泰米尔语
57 | te: "te,te-in",
58 | // 泰卢固语
59 | fil: "fil,fil-ph",
60 | // 菲律宾语
61 | it: "it,it-ch,it-it",
62 | // 意大利语
63 | uk: "uk,uk-ua",
64 | // 乌克兰语
65 | be: "be,be-by",
66 | // 白俄罗斯语
67 | kk: "kk,kk-kz",
68 | // 哈萨克语
69 | fa: "fa,fa-ir",
70 | // 波斯语
71 | da: "da,da-dk",
72 | // 丹麦语
73 | ml: "ml,ml-in",
74 | // 马拉雅拉姆语
75 | or: "or,or-in",
76 | // 奥里雅语
77 | as: "as,as-in",
78 | // 阿萨姆语
79 | ka: "ka,ka-ge",
80 | // 格鲁吉亚语
81 | sa: "sa,sa-in",
82 | // 梵语
83 | uz: "uz,uz-latn,uz-latn-uz,uz-cyrl,uz-cyrl-uz",
84 | // 乌兹别克语
85 | hne: "hne",
86 | // 恰蒂斯加尔语
87 | jv: "jv",
88 | // 爪哇语
89 | raj: "raj",
90 | // 拉贾斯坦语
91 | km: "km,kh,km-kh,khm",
92 | // 高棉语
93 | fr: "fr,fr-be,fr-ca,fr-fr,fr-lu,fr-mc,fr-ch,fra,fre",
94 | //法语
95 | si: "si,sin" //僧伽罗语
96 | /**
97 | * @description ISO 3166-1 国家码映射(facebook标准)
98 | * @see https://zh.wikipedia.org/wiki/ISO_3166-1?fbclid=IwAR1l_8YPkeiFg8S5-n1_iiBq0K4gIBJyBBKGOcCfn16qF5I_hdzDRXEH2nU
99 | */
100 | };
101 |
--------------------------------------------------------------------------------
/src/content/components/check.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import inspect from './lang-inspector'
4 | import Papa from 'papaparse'
5 |
6 | /**
7 | * 解析google表格链接
8 | */
9 | function parseGoogleDocUrl(url) {
10 | var urlParts = url.split('/')
11 | var hashPart = urlParts.pop()
12 | var docUrl = urlParts.join('/')
13 | var id = urlParts.pop()
14 | var hash = hashPart.split('#').pop()
15 | var downurl = docUrl + '/export?format=csv&id=' + id + '&' + hash
16 | return downurl
17 | }
18 |
19 | function Ajax(url, method, data) {
20 | let promise = new Promise((resolve, reject) => {
21 | var xhr = new XMLHttpRequest();
22 | xhr.onreadystatechange = function() {
23 | if (xhr.readyState === 4) {
24 | if (xhr.status === 200) {
25 | resolve(xhr.response);
26 | } else {
27 | reject(new Error("error"));
28 | }
29 | }
30 | }
31 | if (method.toUpperCase() === "GET") {
32 | let paramsList = [];
33 | for (let key in data) {
34 | paramsList.push(key + "=" + data[key]);
35 | }
36 | let params = paramsList.join("&");
37 | url = url + params;
38 | xhr.open("get", url, false);
39 | xhr.send();
40 | }
41 | else if (method.toUpperCase() === "POST") {
42 | xhr.open("post", url, false);
43 | xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
44 | xhr.send(data);
45 | }
46 | })
47 | return promise;
48 | }
49 |
50 | export function check(url) {
51 | return Ajax(parseGoogleDocUrl(url), 'GET').then(res => {
52 | if (!res) {
53 | return res
54 | }
55 | const parseObj = Papa.parse(res)
56 | console.log(res, parseObj, 111);
57 | let arr = parseObj && parseObj.data || []
58 | let deleteCount = 0
59 | for (let i = arr[0].length - 1; i >= 0; i--) {
60 | let empty = true
61 | for (let j = 0; j < arr.length; j++) {
62 | if (arr[j][i].trim()) {
63 | empty = false
64 | break
65 | }
66 | }
67 | if (empty) {
68 | deleteCount++
69 | } else {
70 | break
71 | }
72 | }
73 | if (deleteCount) {
74 | arr = arr.map(it => it.slice(0, -deleteCount))
75 | }
76 | console.log(arr)
77 | const toArr = []
78 | const h = arr.length || 0
79 | const w = arr[0] && arr[0].length || 0
80 |
81 | for (let i = 0; i < h; i++) {
82 | for (let j = 0; j < w; j++) {
83 | if (!toArr[j]) {
84 | toArr[j] = []
85 | }
86 | toArr[j][i] = arr[i][j]
87 | }
88 | }
89 | const report = inspect(toArr)
90 | return {
91 | status: 0,
92 | result: report
93 | };
94 | })
95 | }
--------------------------------------------------------------------------------
/src/content/components/sheetToCode.js:
--------------------------------------------------------------------------------
1 | function htmlToArr(text) {
2 | let dom = document.createElement(`div`);
3 | dom.innerHTML = text;
4 | dom = dom.querySelector("table tbody");
5 | if (!dom) {
6 | return [];
7 | }
8 | // raw arr
9 | const cellArr = Array.prototype.map.call(dom.children || [], it => {
10 | return Array.prototype.map.call(it.children || [], cell => {
11 | return {
12 | row: cell.getAttribute("rowspan") - 0 || 1,
13 | col: cell.getAttribute("colspan") - 0 || 1,
14 | val: cell.innerText
15 | };
16 | });
17 | });
18 |
19 | // map arr
20 | for (let i = 0; i < cellArr.length; i++) {
21 | const row = cellArr[i];
22 | for (let j = 0; j < row.length; j++) {
23 | const cell = row[j];
24 | const id = cell.id || `${i}-${j}`;
25 | if (cell.col > 1) {
26 | row.splice(j + 1, 0, {
27 | ...cell,
28 | id,
29 | col: cell.col - 1
30 | });
31 | }
32 | if (cell.row > 1) {
33 | cellArr[i + 1].splice(j, 0, {
34 | ...cell,
35 | id,
36 | row: cell.row - 1
37 | });
38 | }
39 | row[j] = {
40 | id,
41 | val: cell.val
42 | };
43 | }
44 | }
45 | return cellArr;
46 | }
47 |
48 | // 双键json
49 | // row0,col0不应该有重复键 todo
50 | function arrToJson_doublekey(arr, major = "col") {
51 | if (arr.length < 2 || arr[0].length < 2) {
52 | return {};
53 | }
54 | if (major === "row") {
55 | const body = arr.slice(1);
56 | const obj = arr[0].slice(1).reduce((res, cur, idx) => {
57 | const colsObj = body.reduce((cols, it) => {
58 | cols[it[0].val] = it[idx + 1].val;
59 | return cols;
60 | }, {});
61 | res[cur.val] = colsObj;
62 | return res;
63 | }, {});
64 | return obj;
65 | } else {
66 | const subKey = arr[0].slice(1);
67 | const body = arr.slice(1);
68 | const obj = body.reduce((res, cur) => {
69 | const rowObj = cur.slice(1).reduce((rows, it, idx) => {
70 | rows[subKey[idx].val] = it.val;
71 | return rows;
72 | }, {});
73 | res[cur[0].val] = rowObj;
74 | return res;
75 | }, {});
76 | return obj;
77 | }
78 | }
79 |
80 | // 单键json
81 | function arrToJson(arr, major = "col") {
82 | if (arr.length < 2 || arr[0].length < 2) {
83 | return {};
84 | }
85 | if (major === "col") {
86 | return arr.reduce((res, cur) => {
87 | res[cur[0].val] = cur.slice(1).map(it => it.val);
88 | return res;
89 | }, {});
90 | }
91 | if (major === "row") {
92 | const body = arr.slice(1);
93 | return arr[0].reduce((res, cur, idx) => {
94 | res[cur.val] = body.map(it => it[idx].val);
95 | return res;
96 | }, {});
97 | }
98 | }
99 |
100 | // 双键json转php
101 | function dbkeyJsonToPhpCode(json) {
102 | const keys = Object.keys(json);
103 | const subKeys = Object.keys(json[keys[0]]);
104 | return keys
105 | .map(it => {
106 | const item = json[it];
107 | const val = subKeys
108 | .map(
109 | subKey =>
110 | `\t"${(subKey || "").replace(/"/g, '\\"')}"=>"${(
111 | item[subKey] || ""
112 | ).replace(/"/g, '\\"')}"`
113 | )
114 | .join(",\n");
115 | return `"${(it || "").replace(/"/g, '\\"')}" => [\n${val}\n]`;
116 | })
117 | .join(",\n");
118 | }
119 |
120 | // 单键json转php
121 | function jsonToPhpCode(json) {
122 | const keys = Object.keys(json);
123 | const rows = keys
124 | .map(it => {
125 | const item = json[it];
126 | return `\t"${(it || "").replace(/"/g, '\\"')}" => [${item
127 | .map(it => `"${(it || "").replace(/"/g, '\\"')}"`)
128 | .join(", ")}]`;
129 | })
130 | .join(",\n");
131 | return `[\n${rows}\n]`;
132 | }
133 |
134 | // 双键转xml文档
135 | function jsonToXmlObj(json) {
136 | return Object.keys(json).reduce((res, it) => {
137 | const data = json[it];
138 | const list = Object.keys(data).map(
139 | item => `${data[item]}`
140 | );
141 | res[it] = `
142 |
143 |
144 |
145 | ${list.join("\n ")}
146 | `;
147 | return res;
148 | }, {});
149 | }
150 |
151 | export default function htmlTransform(text) {
152 | const arr = htmlToArr(text);
153 | if (arr.length === 0) {
154 | alert(
155 | "操作可能失败!如果文档表格有背景色,请将删除背景色或者将该文档**剔除格式**拷贝到新文档"
156 | );
157 | }
158 | const dbkeyJson_row = arrToJson_doublekey(arr, "row");
159 | const dbkeyJson_col = arrToJson_doublekey(arr, "col");
160 | const json_row = arrToJson(arr, "row");
161 | const json_col = arrToJson(arr, "col");
162 | const dbkeyPhpRow = dbkeyJsonToPhpCode(dbkeyJson_row);
163 | const dbkeyPhpCol = dbkeyJsonToPhpCode(dbkeyJson_col);
164 | const jsonPhpRow = jsonToPhpCode(json_row);
165 | const jsonPhpCol = jsonToPhpCode(json_col);
166 | const xmlObj = jsonToXmlObj(dbkeyJson_row);
167 | return {
168 | dbkeyJson_row,
169 | dbkeyJson_col,
170 | json_row,
171 | json_col,
172 | dbkeyPhpRow,
173 | dbkeyPhpCol,
174 | jsonPhpRow,
175 | jsonPhpCol,
176 | xmlObj
177 | };
178 | }
179 |
--------------------------------------------------------------------------------
/src/content/components/lang-inspector/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const _config = require("./config")
4 | const lm = require("./langMap")
5 | let config = {}
6 | // 语言别名映射
7 | const langMap = Object.keys(lm).reduce((res, cur) => {
8 | lm[cur].split(",").forEach((it) => {
9 | if (res[it]) {
10 | console.error("配置文件存在相同的语言别名!", res[it])
11 | }
12 | res[it] = cur
13 | })
14 | return res
15 | }, {})
16 |
17 | // 读行
18 | function readRow(arr, idx) {
19 | if (!Array.isArray(arr)) {
20 | return []
21 | }
22 | return arr.map(
23 | (it) => (Array.isArray(it) && it.length > idx && it[idx]) || ""
24 | )
25 | }
26 |
27 | // 检查语言名
28 | let langCount = 0
29 | let langValid = 0
30 | function checkLangRow(langRow) {
31 | const errorList = []
32 | langCount = langRow.length
33 | langRow.forEach((it, idx) => {
34 | let exist = lm[it]
35 | if (exist) {
36 | langValid++
37 | return
38 | }
39 | // 语言名大写
40 | exist = lm[it.toLowerCase()]
41 | if (exist) {
42 | errorList.push({
43 | pos: `1-${String.fromCharCode(65 + idx + 1)}`,
44 | level: "error",
45 | msg: `请使用小写的语言名,修改【${it}】=>【${it.toLowerCase()}】`
46 | })
47 | return
48 | }
49 | // 写为别名
50 | exist = langMap[it.toLowerCase()]
51 | if (exist) {
52 | errorList.push({
53 | pos: `1-${String.fromCharCode(65 + idx + 1)}`,
54 | level: "error",
55 | msg: `请修改语言别名,修改【${it}】=>【${exist}】`
56 | })
57 | return
58 | }
59 | // 不存在
60 | errorList.push({
61 | pos: `1-${String.fromCharCode(65 + idx + 1)}`,
62 | level: "error",
63 | msg: `不存在该语言别名【${it}】,请检查修改!`
64 | })
65 | })
66 | return errorList
67 | }
68 |
69 | // 检查一个多语言
70 | function checkMulLang(row, rowIdx, uilangIdx) {
71 | const result = []
72 | const uilang = row[uilangIdx]
73 | const params = uilang.match(/\[\d+\]/g) || []
74 | // 非数字变量,排除{001}、{abc}、[abc]、[ab_001]正则式排除中括号影响
75 | if (/\[[^\]\[]*[^\d\]]+[^\]\[]*\]/i.test(uilang)) {
76 | result.push({
77 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + uilangIdx + 1)}`,
78 | level: "error",
79 | msg: `变量命名不规范,不允许[abc]、[ab_001]的命名方式,将【${
80 | /\[[^\]\[]*[^\d\]]+[^\]\[]*\]/i.exec(uilang)[0]
81 | }】修改为[001]格式命名`
82 | })
83 | }
84 | // 检查变量序号
85 | const codes = params.map((it) => it.slice(1, -1)).sort()
86 | if (codes.length > 0 && codes[0] != 1) {
87 | result.push({
88 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + uilangIdx + 1)}`,
89 | level: "error",
90 | msg: `变量命名不规范,请使用从001开始的变量,例如:[001]`
91 | })
92 | }
93 | for (let i = 1; i < codes.length; i++) {
94 | if (codes[i] - codes[i - 1] != 1) {
95 | result.push({
96 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + uilangIdx + 1)}`,
97 | level: "error",
98 | msg: `变量命名不规范,多个变量名需要使用递增的数字序号,例如:[001]、[002]、..`
99 | })
100 | break
101 | }
102 | }
103 | // 提示[01]、[0001]格式错误
104 | params.some((it) => {
105 | if (it.length !== 5) {
106 | result.push({
107 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + uilangIdx + 1)}`,
108 | level: "error",
109 | msg: `变量命名不规范,序号不能是${it.length -
110 | 2}个数字,应改为3位数字,例如:[001]、[002]、..`
111 | })
112 | return true
113 | }
114 | return false
115 | })
116 | // 检查多语言命名规范
117 | if (/[(({【]\w+[))}】]/.test(uilang)) {
118 | result.push({
119 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + uilangIdx + 1)}`,
120 | level: "error",
121 | msg: `变量命名不规范,请使用中括号和数字序号。例如:[001]`
122 | })
123 | }
124 | // 检查空
125 | row.forEach((item, index) => {
126 | if (!item.trim()) {
127 | result.push({
128 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + index + 1)}`,
129 | level: "error",
130 | msg: `不能为空!`
131 | })
132 | }
133 | })
134 | // 检查参数不匹配
135 | row.forEach((item, index) => {
136 | params.forEach((it) => {
137 | if (item.indexOf(it) === -1) {
138 | result.push({
139 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + index + 1)}`,
140 | level: "error",
141 | msg: `多语言缺少参数${it}`
142 | })
143 | }
144 | })
145 | })
146 | // 检查短文本
147 | const short = config.shortTextCompare
148 | if (!short) {
149 | return result
150 | }
151 | const min = Math.min(short.which[0], short.which[1])
152 | const max = Math.max(short.which[0], short.which[1])
153 | const scale = short.scale || 1.6
154 | if (uilang.length >= min && uilang.length <= max) {
155 | let notExpect = null
156 | let lastScale = 1
157 | row.forEach((item, index) => {
158 | if (item.length > uilang.length && item.length / uilang.length > scale) {
159 | const scale = item.length / uilang.length
160 | if (scale > lastScale) {
161 | notExpect = {
162 | pos: `${rowIdx + 1}-${String.fromCharCode(65 + index + 1)}`,
163 | level: "warn",
164 | msg: `短文本(${uilang}), 出现${scale.toFixed(2)}倍长度的文本(${item})`
165 | }
166 | lastScale = scale
167 | }
168 | }
169 | })
170 | if (notExpect) {
171 | result.push(notExpect)
172 | }
173 | }
174 | return result
175 | }
176 |
177 | // 处理
178 | export default function inspect(langData, conf) {
179 | langCount = 0
180 | langValid = 0
181 | config = Object.assign({}, _config, conf)
182 | const result = {
183 | warnList: [],
184 | reportList: []
185 | }
186 | // 有效行,即需要处理的行
187 | const validRow = []
188 | const keyCol = langData[0]
189 | const dataCols = langData.slice(1)
190 | let uilangIdx = 0
191 |
192 | // 检查文件命名
193 | if (keyCol[0] !== "lang") {
194 | result.warnList.push({
195 | pos: "1-A",
196 | level: "error",
197 | msg: `首行第一格必须填写为【lang】, 当前值为【${keyCol[0]}】`
198 | })
199 | }
200 |
201 | // 检查语言行
202 | const langRow = readRow(dataCols, 0)
203 | result.langList = langRow || []
204 | result.warnList.push(...checkLangRow(langRow))
205 | langRow.forEach((it, idx) => {
206 | if (it.toLowerCase() === config.uiLang.toLowerCase()) {
207 | uilangIdx = idx
208 | }
209 | })
210 | const keySet = new Set()
211 | keyCol.slice(1).forEach((key, index) => {
212 | if (keySet.has(key.toLowerCase())) {
213 | result.warnList.push({
214 | pos: `${2 + index}-A`,
215 | level: "error",
216 | msg: `存在重复键【${key}】, 请检查修改!`
217 | })
218 | } else {
219 | keySet.add(key.toLowerCase())
220 | }
221 | if (/^lang\d+$/.test(key)) {
222 | validRow.push(index + 1)
223 | }
224 | })
225 | result.reportList.push({
226 | msg: `检查到的多语言项${
227 | validRow.length
228 | }个,共[${langValid}/${langCount}]种语言`
229 | })
230 | // 依次检查单个多语言
231 | validRow.forEach((it) => {
232 | const langRow = readRow(dataCols, it)
233 | result.warnList.push(...checkMulLang(langRow, it, uilangIdx))
234 | })
235 | result.status = result.warnList.length === 0
236 | return result
237 | }
--------------------------------------------------------------------------------
/src/content/components/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
x
4 |
5 |
6 |
9 |
10 |
15 | json
16 |
17 |
22 | php
23 |
24 |
25 |
26 |
27 |
double key json:
28 |
29 |
{{
30 | getJsonText(data.dbkeyJson_col)
31 | }}
32 |
36 | copy
37 |
38 |
39 |
40 |
{{
41 | getJsonText(data.dbkeyJson_row)
42 | }}
43 |
47 | copy
48 |
49 |
50 |
one key json:
51 |
52 |
{{
53 | getJsonText(data.json_col)
54 | }}
55 |
56 | copy
57 |
58 |
59 |
60 |
{{
61 | getJsonText(data.json_row)
62 | }}
63 |
64 | copy
65 |
66 |
67 |
68 |
69 |
double key php:
70 |
71 |
{{ data.dbkeyPhpCol }}
72 |
copy
73 |
74 |
75 |
{{ data.dbkeyPhpRow }}
76 |
copy
77 |
78 |
one key php:
79 |
80 |
{{ data.jsonPhpCol }}
81 |
copy
82 |
83 |
84 |
{{ data.jsonPhpRow }}
85 |
copy
86 |
87 |
88 |
89 |
90 |
91 |

96 |
该程序用于将表格复制文本转换为json/php代码。
97 |
选择表格区域复制,在此次导出代码。
98 |
在表格中使用 ctrl + a, ctrl + c 选中并复制整个表格
99 |
100 | 如果文档表格有背景,请将删除背景或者将该文档剔除背景拷贝到新文档
101 |
102 |
103 | 打开
104 |
示例并重新打开小程序尝试。
109 |
110 |
111 |
112 |
113 |
114 |
115 |
120 |

125 |
126 | 输入多语言Excel表格链接
127 |
128 |
134 | {{ item.msg }}
135 |
136 |
137 |
empty!
138 |
144 | {{ item }}
145 |
146 |
147 |
153 |
154 | verbose
155 | error
156 | warn
157 |
158 |
159 |
160 |
166 |
167 |
168 |
169 |
没检查到问题
170 |
174 | 该选项不存在问题
175 |
176 |
182 | {{ warn.level }}
183 | {{ warn.pos }}
184 | {{ warn.msg }}
185 |
186 |
187 |
188 |
189 |
190 |
191 |
323 |
324 |
603 |
--------------------------------------------------------------------------------