From 9fde73daddf3d00af727e764c66dc4e7ef7fbffa Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Fri, 28 Oct 2022 11:55:31 +0200 Subject: [PATCH 1/6] Tools(extract): update build --- banner.txt | 6 + package-lock.json | 351 +++++++++++------------------------ package.json | 11 +- tools/extract/cli.js | 5 - tools/extract/cli.ts | 8 + tools/extract/extract.ts | 5 - tools/extract/index.ts | 11 +- tools/extract/types.ts | 37 ++++ tools/vite.config.extract.ts | 28 +-- tools/vite.config.inline.ts | 6 +- tsconfig.json | 2 +- vite.config.lib.ts | 6 + 12 files changed, 203 insertions(+), 273 deletions(-) create mode 100644 banner.txt delete mode 100644 tools/extract/cli.js create mode 100644 tools/extract/cli.ts delete mode 100644 tools/extract/extract.ts create mode 100644 tools/extract/types.ts diff --git a/banner.txt b/banner.txt new file mode 100644 index 0000000..9e4e43f --- /dev/null +++ b/banner.txt @@ -0,0 +1,6 @@ +/** + * @license + * Qwik Speak + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/robisim74/qwik-speak/blob/main/LICENSE + */ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 74c04d7..0485c67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,10 +28,10 @@ "jest": "^29.1.1", "node-fetch": "3.2.10", "np": "7.6.2", + "rollup-plugin-add-shebang": "^0.3.1", "ts-jest": "^29.0.3", "typescript": "4.8.4", - "vite": "3.1.7", - "vite-plugin-static-copy": "^0.9.0", + "vite": "3.2.0", "vite-tsconfig-paths": "3.5.0" }, "engines": { @@ -2399,15 +2399,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", @@ -2765,45 +2756,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/ci-info": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", @@ -5555,18 +5507,6 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", @@ -7574,6 +7514,15 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9630,9 +9579,9 @@ } }, "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", "dev": true, "funding": [ { @@ -9946,18 +9895,6 @@ "node": ">=8" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/recrawl-sync": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recrawl-sync/-/recrawl-sync-2.2.2.tgz", @@ -10189,9 +10126,9 @@ } }, "node_modules/rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -10203,6 +10140,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-add-shebang": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-add-shebang/-/rollup-plugin-add-shebang-0.3.1.tgz", + "integrity": "sha512-tKONSgKoVw9Om1cp1CnAlPQ9nsHBzu8fInKObX3zT5KZVoAJtslD1aBL84lJuKLeh+L28dB26CBBeYT+doTMLg==", + "dev": true, + "dependencies": { + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -10470,6 +10432,12 @@ "node": ">=0.10.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "node_modules/space-separated-tokens": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", @@ -11443,15 +11411,15 @@ } }, "node_modules/vite": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.7.tgz", - "integrity": "sha512-5vCAmU4S8lyVdFCInu9M54f/g8qbOMakVw5xJ4pjoaDy5wgy9sLLZkGdSLN52dlsBqh0tBqxjaqqa8LgPqwRAA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.0.tgz", + "integrity": "sha512-Ovj7+cqIdM1I0LPCk2CWxzgADXMix3NLXpUT6g7P7zg/a9grk/TaC3qn9YMg7w7M0POIVCBOp1aBANJW+RH7oA==", "dev": true, "dependencies": { "esbuild": "^0.15.9", - "postcss": "^8.4.16", + "postcss": "^8.4.18", "resolve": "^1.22.1", - "rollup": "~2.78.0" + "rollup": "^2.79.1" }, "bin": { "vite": "bin/vite.js" @@ -11466,6 +11434,7 @@ "less": "*", "sass": "*", "stylus": "*", + "sugarss": "*", "terser": "^5.4.0" }, "peerDependenciesMeta": { @@ -11478,64 +11447,14 @@ "stylus": { "optional": true }, + "sugarss": { + "optional": true + }, "terser": { "optional": true } } }, - "node_modules/vite-plugin-static-copy": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-0.9.0.tgz", - "integrity": "sha512-0h8esPoZn6zdTK8KoDbiZPJum7+Nw2t4oLTpu2i2haP2HmgysRH+Xy6FaE6lLyCpgODYR5RiRjbLH0UKUZpIPw==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.3", - "fast-glob": "^3.2.11", - "fs-extra": "^10.1.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^3.0.0" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/vite-plugin-static-copy/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/vite-tsconfig-paths": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-3.5.0.tgz", @@ -13639,12 +13558,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", @@ -13899,33 +13812,6 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, "ci-info": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", @@ -15911,15 +15797,6 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", @@ -17425,6 +17302,15 @@ "yallist": "^4.0.0" } }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -18841,9 +18727,9 @@ "dev": true }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -19067,15 +18953,6 @@ } } }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "recrawl-sync": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recrawl-sync/-/recrawl-sync-2.2.2.tgz", @@ -19244,14 +19121,41 @@ } }, "rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", "dev": true, "requires": { "fsevents": "~2.3.2" } }, + "rollup-plugin-add-shebang": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-add-shebang/-/rollup-plugin-add-shebang-0.3.1.tgz", + "integrity": "sha512-tKONSgKoVw9Om1cp1CnAlPQ9nsHBzu8fInKObX3zT5KZVoAJtslD1aBL84lJuKLeh+L28dB26CBBeYT+doTMLg==", + "dev": true, + "requires": { + "magic-string": "^0.25.3", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + }, + "dependencies": { + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + } + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -19462,6 +19366,12 @@ } } }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "space-separated-tokens": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", @@ -20155,57 +20065,16 @@ } }, "vite": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.7.tgz", - "integrity": "sha512-5vCAmU4S8lyVdFCInu9M54f/g8qbOMakVw5xJ4pjoaDy5wgy9sLLZkGdSLN52dlsBqh0tBqxjaqqa8LgPqwRAA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.0.tgz", + "integrity": "sha512-Ovj7+cqIdM1I0LPCk2CWxzgADXMix3NLXpUT6g7P7zg/a9grk/TaC3qn9YMg7w7M0POIVCBOp1aBANJW+RH7oA==", "dev": true, "requires": { "esbuild": "^0.15.9", "fsevents": "~2.3.2", - "postcss": "^8.4.16", + "postcss": "^8.4.18", "resolve": "^1.22.1", - "rollup": "~2.78.0" - } - }, - "vite-plugin-static-copy": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-0.9.0.tgz", - "integrity": "sha512-0h8esPoZn6zdTK8KoDbiZPJum7+Nw2t4oLTpu2i2haP2HmgysRH+Xy6FaE6lLyCpgODYR5RiRjbLH0UKUZpIPw==", - "dev": true, - "requires": { - "chokidar": "^3.5.3", - "fast-glob": "^3.2.11", - "fs-extra": "^10.1.0", - "picocolors": "^1.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } + "rollup": "^2.79.1" } }, "vite-tsconfig-paths": { diff --git a/package.json b/package.json index 64cf356..f3ab4bc 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,11 @@ "test": "jest ./src/tests ./tools/tests", "test.e2e": "playwright test", "test.watch": "jest ./src/tests ./tools/tests --watch", - "qwik": "qwik" + "qwik": "qwik", + "qwik-speak-extract": "qwik-speak-extract" }, "bin": { - "qwik-speak-extract": "./extract/cli.js" + "qwik-speak-extract": "./extract/cli.js --supportedLangs=en-US,it-IT --defaultLang=en-US" }, "peerDependencies": { "@builder.io/qwik": ">=0.11.0" @@ -48,8 +49,8 @@ "np": "7.6.2", "ts-jest": "^29.0.3", "typescript": "4.8.4", - "vite-plugin-static-copy": "^0.9.0", - "vite": "3.1.7", + "rollup-plugin-add-shebang": "^0.3.1", + "vite": "3.2.0", "vite-tsconfig-paths": "3.5.0" }, "engines": { @@ -95,4 +96,4 @@ "qwik": "./lib/index.qwik.mjs", "type": "module", "types": "./lib/index.d.ts" -} +} \ No newline at end of file diff --git a/tools/extract/cli.js b/tools/extract/cli.js deleted file mode 100644 index da8a453..0000000 --- a/tools/extract/cli.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { extract } from './index.mjs'; - -extract(); diff --git a/tools/extract/cli.ts b/tools/extract/cli.ts new file mode 100644 index 0000000..d00633d --- /dev/null +++ b/tools/extract/cli.ts @@ -0,0 +1,8 @@ +import { extract } from './index'; + +const args = process.argv; + +extract({ + supportedLangs: [], + defaultLang: '' +}); diff --git a/tools/extract/extract.ts b/tools/extract/extract.ts deleted file mode 100644 index 0fc61bb..0000000 --- a/tools/extract/extract.ts +++ /dev/null @@ -1,5 +0,0 @@ -export async function extract() { - const args = process.argv; - - // TODO -} diff --git a/tools/extract/index.ts b/tools/extract/index.ts index b96ed90..22ef782 100644 --- a/tools/extract/index.ts +++ b/tools/extract/index.ts @@ -1 +1,10 @@ -export { extract } from './extract'; +import type { QwikSpeakExtractOptions } from './types'; + +/** + * Extract translations from source files + */ +export async function extract(options: QwikSpeakExtractOptions) { + // TODO +} + +export type { QwikSpeakExtractOptions }; diff --git a/tools/extract/types.ts b/tools/extract/types.ts new file mode 100644 index 0000000..00ba473 --- /dev/null +++ b/tools/extract/types.ts @@ -0,0 +1,37 @@ +/** + * Qwik Speak Extract Command Options + */ +export interface QwikSpeakExtractOptions { + /** + * The base path. Default to './' + */ + basePath?: string; + /** + * Path to files to search for translations. Default to 'src' + */ + sourceFilesPath?: string; + /** + * Path to translation files: [basePath]/[assetsPath]/[lang]/*.json. Default to 'public/i18n' + */ + assetsPath?: string; + /** + * The format of the translation files. Default to 'json' + */ + format?: 'json'; + /** + * Supported langs. Required + */ + supportedLangs: string[]; + /** + * Default lang. Required + */ + defaultLang: string; + /** + * Separator of nested keys. Default is '.' + */ + keySeparator?: string; + /** + * Key-value separator. Default is '@@' + */ + keyValueSeparator?: string; +} diff --git a/tools/vite.config.extract.ts b/tools/vite.config.extract.ts index 6682537..41e948e 100644 --- a/tools/vite.config.extract.ts +++ b/tools/vite.config.extract.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; -import { viteStaticCopy } from 'vite-plugin-static-copy' +import shebang from 'rollup-plugin-add-shebang'; +import { readFile } from 'fs/promises'; export default defineConfig(() => { return { @@ -8,27 +9,26 @@ export default defineConfig(() => { outDir: 'extract', target: 'es2020', lib: { - entry: 'tools/extract/index.ts', + entry: ['tools/extract/index.ts', 'tools/extract/cli.ts'], formats: ['es', 'cjs'], - fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`, + fileName: (format, entryName) => `${entryName}.${format === 'es' ? 'mjs' : 'cjs'}`, }, rollupOptions: { + output: { + banner: () => readFile('./banner.txt', 'utf8') + }, external: [ 'fs', 'fs/promises', 'path' + ], + plugins: [ + shebang({ + shebang: '#!/usr/bin/env node', + include: ['./extract/cli.mjs', './extract/cli.cjs'] + }) ] } - }, - plugins: [ - viteStaticCopy({ - targets: [ - { - src: 'tools/extract/cli.js', - dest: './' - } - ] - }) - ] + } }; }); diff --git a/tools/vite.config.inline.ts b/tools/vite.config.inline.ts index 27867e0..771413d 100644 --- a/tools/vite.config.inline.ts +++ b/tools/vite.config.inline.ts @@ -1,4 +1,5 @@ import { defineConfig } from 'vite'; +import { readFile } from 'fs/promises'; export default defineConfig(() => { return { @@ -9,9 +10,12 @@ export default defineConfig(() => { lib: { entry: 'tools/inline/index.ts', formats: ['es', 'cjs'], - fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`, + fileName: (format, entryName) => `${entryName}.${format === 'es' ? 'mjs' : 'cjs'}`, }, rollupOptions: { + output: { + banner: () => readFile('./banner.txt', 'utf8') + }, external: [ 'fs', 'fs/promises', diff --git a/tsconfig.json b/tsconfig.json index 4212a05..6c65748 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ "qwik-speak": ["./src/index.ts"] } }, - "include": ["src", "tools/**/*.ts"] + "include": ["src", "tools"] } diff --git a/vite.config.lib.ts b/vite.config.lib.ts index 5abbcf9..6b0ee78 100644 --- a/vite.config.lib.ts +++ b/vite.config.lib.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; import { qwikVite } from '@builder.io/qwik/optimizer'; +import { readFile } from 'fs/promises'; export default defineConfig(() => { return { @@ -11,6 +12,11 @@ export default defineConfig(() => { formats: ['es', 'cjs'], fileName: (format) => `index.qwik.${format === 'es' ? 'mjs' : 'cjs'}`, }, + rollupOptions: { + output: { + banner: () => readFile('./banner.txt', 'utf8') + } + } }, plugins: [ qwikVite(), From daf6dbefe24a0151f26e2f1387185831f6dd54d2 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Fri, 4 Nov 2022 16:36:24 +0100 Subject: [PATCH 2/6] Tools(extract): cli & function --- package.json | 4 +- public/i18n/en-US/app.json | 6 +- public/i18n/en-US/home.json | 10 +- public/i18n/en-US/runtime.json | 24 +-- public/i18n/it-IT/app.json | 6 +- public/i18n/it-IT/home.json | 10 +- public/i18n/it-IT/page.json | 4 +- public/i18n/it-IT/runtime.json | 24 +-- src/app/routes/[...lang]/index.tsx | 6 +- src/app/routes/[...lang]/page/index.tsx | 4 +- tools/core/cli-parser.ts | 12 ++ tools/core/format.ts | 28 ++++ tools/core/merge.ts | 24 +++ tools/core/parser.ts | 15 ++ tools/extract/cli.ts | 80 ++++++++- tools/extract/index.ts | 208 +++++++++++++++++++++++- tools/extract/types.ts | 11 +- tools/inline/plugin.ts | 24 +-- tools/tests/inline.test.ts | 18 +- tools/tests/parser.test.ts | 21 ++- tools/tsconfig.json | 2 +- tools/vite.config.extract.ts | 6 +- 22 files changed, 449 insertions(+), 98 deletions(-) create mode 100644 tools/core/cli-parser.ts create mode 100644 tools/core/format.ts create mode 100644 tools/core/merge.ts diff --git a/package.json b/package.json index f3ab4bc..b2674a5 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "test.e2e": "playwright test", "test.watch": "jest ./src/tests ./tools/tests --watch", "qwik": "qwik", - "qwik-speak-extract": "qwik-speak-extract" + "qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT --sourceFilesPath=src/app" }, "bin": { - "qwik-speak-extract": "./extract/cli.js --supportedLangs=en-US,it-IT --defaultLang=en-US" + "qwik-speak-extract": "./extract/cli.js" }, "peerDependencies": { "@builder.io/qwik": ">=0.11.0" diff --git a/public/i18n/en-US/app.json b/public/i18n/en-US/app.json index 4c45ba8..05a534e 100644 --- a/public/i18n/en-US/app.json +++ b/public/i18n/en-US/app.json @@ -1,11 +1,11 @@ { "app": { - "title": "Qwik Speak", - "subtitle": "Translate your Qwik apps into any language", "changeLocale": "Change locale", "nav": { "home": "Home", "page": "Page" - } + }, + "subtitle": "Translate your Qwik apps into any language", + "title": "Qwik Speak" } } \ No newline at end of file diff --git a/public/i18n/en-US/home.json b/public/i18n/en-US/home.json index 2ef084b..c1c0801 100644 --- a/public/i18n/en-US/home.json +++ b/public/i18n/en-US/home.json @@ -1,12 +1,12 @@ { "home": { + "dates": "Dates & relative time", "greeting": "Hi! I am {{name}}", - "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps", + "increment": "Increment", + "numbers": "Numbers & currencies", "params": "Parameters", - "tags": "Html tags", "plural": "Plural", - "dates": "Dates & relative time", - "numbers": "Numbers & currencies", - "increment": "Increment" + "tags": "Html tags", + "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" } } \ No newline at end of file diff --git a/public/i18n/en-US/runtime.json b/public/i18n/en-US/runtime.json index 9cfa3ce..2fd7114 100644 --- a/public/i18n/en-US/runtime.json +++ b/public/i18n/en-US/runtime.json @@ -1,16 +1,18 @@ { - "head": { - "home": { - "title": "{{name}}", - "description": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" + "runtime": { + "devs": { + "one": "{{ value }} software developer", + "other": "{{value}} software developers" }, - "page": { - "title": "Page - {{name}}", - "description": "I'm another page" + "head": { + "home": { + "description": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps", + "title": "{{name}}" + }, + "page": { + "description": "I'm another page", + "title": "Page - {{name}}" + } } - }, - "devs": { - "one": "{{ value }} software developer", - "other": "{{value}} software developers" } } \ No newline at end of file diff --git a/public/i18n/it-IT/app.json b/public/i18n/it-IT/app.json index 92fbe3a..d609ead 100644 --- a/public/i18n/it-IT/app.json +++ b/public/i18n/it-IT/app.json @@ -1,11 +1,11 @@ { "app": { - "title": "Qwik Speak", - "subtitle": "Traduci le tue app Qwik in qualsiasi lingua", "changeLocale": "Cambia località", "nav": { "home": "Home", "page": "Pagina" - } + }, + "subtitle": "Traduci le tue app Qwik in qualsiasi lingua", + "title": "Qwik Speak" } } \ No newline at end of file diff --git a/public/i18n/it-IT/home.json b/public/i18n/it-IT/home.json index fb61379..232cec6 100644 --- a/public/i18n/it-IT/home.json +++ b/public/i18n/it-IT/home.json @@ -1,12 +1,12 @@ { "home": { + "dates": "Date e tempo relativo", "greeting": "Ciao! Sono {{name}}", - "text": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik", + "increment": "Incrementa", + "numbers": "Numeri e valute", "params": "Parametri", - "tags": "Tag Html", "plural": "Plurale", - "dates": "Date e tempo relativo", - "numbers": "Numeri e valute", - "increment": "Incrementa" + "tags": "Tag Html", + "text": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik" } } \ No newline at end of file diff --git a/public/i18n/it-IT/page.json b/public/i18n/it-IT/page.json index 8350440..b765c29 100644 --- a/public/i18n/it-IT/page.json +++ b/public/i18n/it-IT/page.json @@ -1,3 +1,5 @@ { - "page": {} + "page": { + "text": "I'm a default value" + } } \ No newline at end of file diff --git a/public/i18n/it-IT/runtime.json b/public/i18n/it-IT/runtime.json index 69231ed..8b8c51b 100644 --- a/public/i18n/it-IT/runtime.json +++ b/public/i18n/it-IT/runtime.json @@ -1,16 +1,18 @@ { - "head": { - "home": { - "title": "{{name}}", - "description": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik" + "runtime": { + "devs": { + "one": "{{ value }} sviluppatore software", + "other": "{{ value }} sviluppatori software" }, - "page": { - "title": "Pagina - {{name}}", - "description": "Io sono un'altra pagina" + "head": { + "home": { + "description": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik", + "title": "{{name}}" + }, + "page": { + "description": "Io sono un'altra pagina", + "title": "Pagina - {{name}}" + } } - }, - "devs": { - "one": "{{ value }} sviluppatore software", - "other": "{{ value }} sviluppatori software" } } \ No newline at end of file diff --git a/src/app/routes/[...lang]/index.tsx b/src/app/routes/[...lang]/index.tsx index 8329423..0d7d6b2 100644 --- a/src/app/routes/[...lang]/index.tsx +++ b/src/app/routes/[...lang]/index.tsx @@ -30,7 +30,7 @@ export const Home = component$(() => {

{t('home.plural')}

-

{p(state.count, 'devs')}

+

{p(state.count, 'runtime.devs')}

{t('home.dates')}

{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}

@@ -56,8 +56,8 @@ export default component$(() => { }); export const head: DocumentHead = { - title: 'head.home.title', - meta: [{ name: 'description', content: 'head.home.description' }] + title: 'runtime.head.home.title', + meta: [{ name: 'description', content: 'runtime.head.home.description' }] }; // E.g. SSG diff --git a/src/app/routes/[...lang]/page/index.tsx b/src/app/routes/[...lang]/page/index.tsx index 689724e..2ac1894 100644 --- a/src/app/routes/[...lang]/page/index.tsx +++ b/src/app/routes/[...lang]/page/index.tsx @@ -27,8 +27,8 @@ export default component$(() => { }); export const head: DocumentHead = { - title: 'head.page.title', - meta: [{ name: 'description', content: 'head.page.description' }] + title: 'runtime.head.page.title', + meta: [{ name: 'description', content: 'runtime.head.page.description' }] }; // E.g. SSG diff --git a/tools/core/cli-parser.ts b/tools/core/cli-parser.ts new file mode 100644 index 0000000..48c6726 --- /dev/null +++ b/tools/core/cli-parser.ts @@ -0,0 +1,12 @@ +/** + * Parse a cli argument to { key: value } + */ +export function parseArgument(arg: string): { key: string, value: any } { + const property = arg.split('='); + if (property.length === 2 && property[0].startsWith('--')) { + const key = property[0].slice(2); + const value = /,/.test(property[1]) ? property[1].split(',') : property[1]; + return { key, value }; + } + return { key: 'error', value: `- wrong option: "${property[0]}"` }; +} diff --git a/tools/core/format.ts b/tools/core/format.ts new file mode 100644 index 0000000..32c973b --- /dev/null +++ b/tools/core/format.ts @@ -0,0 +1,28 @@ +export function toJsonString(target: { [key: string]: any }): string { + return JSON.stringify(target, replacer, 2); +} + +export function minDepth(target: { [key: string]: any }): number { + return typeof target === 'object' && Object.keys(target).length > 0 ? + 1 + Math.min(1, ...Object.values(target).map(o => minDepth(o))) + : 0 +} + +export function sortTarget(target: { [key: string]: any }) { + return Object.keys(target).sort().reduce( + (out: any, key: string) => { + if (typeof target[key] === 'object') + out[key] = sortTarget(target[key]); + else + out[key] = target[key]; + return out; + }, {} + ); +} + +/** + * Remove escaped sequences + */ +function replacer(key: string, value: any) { + return typeof value === 'string' ? value.replace(/\\/g, '') : value; +}; diff --git a/tools/core/merge.ts b/tools/core/merge.ts new file mode 100644 index 0000000..470f45d --- /dev/null +++ b/tools/core/merge.ts @@ -0,0 +1,24 @@ +/** + * https://github.com/lukeed/dset + */ +export function deepSet(target: { [key: string]: any }, keys: string[], val: string) { + let i = 0; + let len = keys.length; + while (i < len) { + let key = keys[i++]; + target[key] = (i === len) ? val : typeof target[key] === 'object' ? target[key] : {}; + target = target[key]; + } +} + +export function deepMerge(target: { [key: string]: any }, source: { [key: string]: any }) { + if (typeof target === 'object' && typeof source === 'object') { + for (const key of Object.keys(source)) { + if (!target[key] || typeof source[key] !== 'object') + target[key] = source[key]; + else + deepMerge(target[key], source[key]); + } + } + return target; +} diff --git a/tools/core/parser.ts b/tools/core/parser.ts index e93f812..3cedf9d 100644 --- a/tools/core/parser.ts +++ b/tools/core/parser.ts @@ -310,3 +310,18 @@ export function parseSequenceExpressions(code: string, alias: string): CallExpre return sequenceExpressions; } + +/** + * Get $translate alias + */ +export function getTranslateAlias(code: string): string { + let translateAlias = code.match(/(?<=\$translate as).*?(?=,|\})/s)?.[0]?.trim() || '$translate'; + // Escape special characters / Assert position at a word boundary + translateAlias = translateAlias.startsWith('$') ? `\\${translateAlias}` : `\\b${translateAlias}`; + return translateAlias; +} + +export function parseJson(target: { [key: string]: any }, source: string): { [key: string]: any } { + target = { ...target, ...JSON.parse(source) }; + return target; +} diff --git a/tools/extract/cli.ts b/tools/extract/cli.ts index d00633d..10a3571 100644 --- a/tools/extract/cli.ts +++ b/tools/extract/cli.ts @@ -1,8 +1,78 @@ +import type { QwikSpeakExtractOptions } from './types'; +import { parseArgument } from '../core/cli-parser'; import { extract } from './index'; -const args = process.argv; +const assertType = (value: any, type: string): boolean => { + if (type === value) return true; + if (type === 'array' && Array.isArray(value)) return true; + if (type === 'string' && typeof (value) === 'string') return true; + return false; +}; -extract({ - supportedLangs: [], - defaultLang: '' -}); +const wrongOption = (key: string, value: any): string => `- option "${key}": wrong value ${JSON.stringify(value)}`; +const missingOption = (name: string): string => `- missing option: "${name}"`; + +const args = process.argv.slice(2); + +const options: Partial = {}; + +const errors: string[] = []; + +// Parse arguments +for (const arg of args) { + const { key, value } = parseArgument(arg); + switch (key) { + case 'basePath': + if (assertType(value, 'string')) options.basePath = value; + else errors.push(wrongOption(key, value)); + break; + case 'sourceFilesPath': + if (assertType(value, 'string')) options.sourceFilesPath = value; + else errors.push(wrongOption(key, value)); + break; + case 'assetsPath': + if (assertType(value, 'string')) options.assetsPath = value; + else errors.push(wrongOption(key, value)); + break; + case 'format': + if (assertType(value, 'json')) options.format = value; + else errors.push(wrongOption(key, value)); + break; + case 'supportedLangs': + if (assertType(value, 'array')) options.supportedLangs = value; + else errors.push(wrongOption(key, value)); + break; + case 'keySeparator': + if (assertType(value, 'string')) options.keySeparator = value; + else errors.push(wrongOption(key, value)); + break; + case 'keyValueSeparator': + if (assertType(value, 'string')) options.keyValueSeparator = value; + else errors.push(wrongOption(key, value)); + break; + case 'error': + errors.push(value); + break; + default: + errors.push(`- unknown option: "${key}"`); + } +} + +// Required options +if (!options.supportedLangs) errors.push(missingOption('supportedLangs')); + +// Log errors +if (errors.length > 0) { + console.log('\x1b[36m%s\x1b[0m', 'Qwik Speak Extract options errors:'); + for (const error of errors) { + console.log('\x1b[33m%s\x1b[0m', error); + } + + process.exitCode = 1; // Exit process +} + +// Process +console.log('\x1b[36m%s\x1b[0m', 'Qwik Speak Extract'); +console.log('\x1b[32m%s\x1b[0m', 'extracting translation...'); + +extract(options as QwikSpeakExtractOptions); diff --git a/tools/extract/index.ts b/tools/extract/index.ts index 22ef782..07f2371 100644 --- a/tools/extract/index.ts +++ b/tools/extract/index.ts @@ -1,10 +1,214 @@ -import type { QwikSpeakExtractOptions } from './types'; +import { readdir, readFile, writeFile } from 'fs/promises'; +import { existsSync, mkdirSync } from 'fs'; +import { extname, join, normalize } from 'path'; + +import type { QwikSpeakExtractOptions, Translation } from './types'; +import { getTranslateAlias, parseJson, parseSequenceExpressions } from '../core/parser'; +import { deepMerge, deepSet } from '../core/merge'; +import { minDepth, sortTarget, toJsonString } from '../core/format'; /** * Extract translations from source files */ export async function extract(options: QwikSpeakExtractOptions) { - // TODO + // Resolve options + const resolvedOptions: Required = { + ...options, + basePath: options.basePath ?? './', + sourceFilesPath: options.sourceFilesPath ?? 'src', + assetsPath: options.assetsPath ?? 'public/i18n', + format: options.format ?? 'json', + keySeparator: options.keySeparator ?? '.', + keyValueSeparator: options.keyValueSeparator ?? '@@', + } + + // Logs + const stats = new Map(); + + const baseSources = normalize(`${resolvedOptions.basePath}/${resolvedOptions.sourceFilesPath}`); + + // Source files + const sourceFiles: string[] = []; + // Translation data + const translation: Translation = Object.fromEntries(resolvedOptions.supportedLangs.map(value => [value, {}])); + + /** + * Read source files recursively + */ + const readSourceFiles = async (sourceFilesPath: string) => { + const files = await readdir(sourceFilesPath, { withFileTypes: true }); + for (const file of files) { + const filePath = join(sourceFilesPath, file.name); + const ext = extname(file.name); + if (file.isDirectory()) { + await readSourceFiles(filePath); + } else if (/\.js|\.ts|\.jsx|\.tsx/.test(ext)) { + sourceFiles.push(filePath); + } + } + }; + + /** + * Parse source file to return keys + */ + const parseSourceFile = async (file: string): Promise => { + const keys: string[] = []; + + const code = await readFile(normalize(`${resolvedOptions.basePath}/${file}`), 'utf8'); + + if (/\$translate/.test(code)) { + const alias = getTranslateAlias(code); + // Parse sequence + const sequence = parseSequenceExpressions(code, alias); + for (const expr of sequence) { + const args = expr.arguments; + + if (args?.[0]?.value) { + if (args[0].type === 'Identifier') { + stats.set('dynamic', (stats.get('dynamic') ?? 0) + 1); + continue; + } + if (args[0].type === 'Literal') { + if (args[0].value !== 'key' && /\${.*}/.test(args[0].value)) { + stats.set('dynamic', (stats.get('dynamic') ?? 0) + 1); + continue; + } + } + if (args[1]?.type === 'Identifier' || args[1]?.type === 'CallExpression' || + args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression' || + args[3]?.type === 'Identifier' || args[3]?.type === 'CallExpression') { + stats.set('dynamic', (stats.get('dynamic') ?? 0) + 1); + continue; + } + + keys.push(args[0].value); + stats.set('keys', (stats.get('keys') ?? 0) + 1); + } + } + } + + return keys; + }; + + /** + * Read, deep merge & sort translation data + */ + const readAssets = async () => { + await Promise.all(resolvedOptions.supportedLangs.map(async lang => { + const baseAssets = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); + + if (!existsSync(baseAssets)) return; + + const files = await readdir(baseAssets); + + if (files.length > 0) { + const ext = extname(files[0]); + let data: Translation = {}; + + const tasks = files.map(filename => readFile(`${baseAssets}/${filename}`, 'utf8')); + const sources = await Promise.all(tasks); + + for (const source of sources) { + if (source) { + switch (ext) { + case '.json': + data = parseJson(data, source); + break; + } + } + } + + deepMerge(translation[lang], data); + + // Sort by key + translation[lang] = sortTarget(translation[lang]); + } + })); + }; + + /** + * Write translation data + * + * Naming convention of keys: + * min depth > 1: filenames = each top-level property name + * min depth = 1: filename = 'app' + */ + const writeAssets = async () => { + for (const lang of resolvedOptions.supportedLangs) { + const baseAssets = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); + + if (!existsSync(baseAssets)) { + mkdirSync(baseAssets, { recursive: true }); + } + + if (minDepth(translation[lang]) > 1) { + for (const topLevelProperty of Object.keys(translation[lang])) { + let data: string; + switch (resolvedOptions.format) { + case 'json': + data = toJsonString({ [topLevelProperty]: translation[lang][topLevelProperty] }); // computed property name + break; + } + const file = normalize(`${baseAssets}/${topLevelProperty}.${resolvedOptions.format}`); + await writeFile(file, data); + console.log(file); + } + } else { + let data: string; + switch (resolvedOptions.format) { + case 'json': + data = toJsonString(translation[lang]); + break; + } + + const file = normalize(`${baseAssets}/app.${resolvedOptions.format}`); + await writeFile(file, data); + console.log(file); + } + } + }; + + /** + * Start pipeline + */ + await readSourceFiles(baseSources); + + const tasks = sourceFiles.map(file => parseSourceFile(file)); + const sources = await Promise.all(tasks); + + let keys: string[] = []; + for (const source of sources) { + keys = keys.concat(source); + } + + // Deep set + for (let key of keys) { + let defaultValue: string | undefined = undefined; + + [key, defaultValue] = key.split(resolvedOptions.keyValueSeparator); + + for (const lang of resolvedOptions.supportedLangs) { + deepSet(translation[lang], key.split(resolvedOptions.keySeparator), defaultValue || ''); + } + } + + // Read, deep merge & sort + await readAssets(); + + // Write + await writeAssets(); + + // Log + for (const [key, value] of stats) { + switch (key) { + case 'keys': + console.log('\x1b[32m%s\x1b[0m', `extracted keys: ${value}`); + break; + case 'dynamic': + console.log('\x1b[32m%s\x1b[0m', `skipped keys due to dynamic params: ${value}`); + break; + } + } } export type { QwikSpeakExtractOptions }; diff --git a/tools/extract/types.ts b/tools/extract/types.ts index 00ba473..aafac70 100644 --- a/tools/extract/types.ts +++ b/tools/extract/types.ts @@ -1,5 +1,5 @@ /** - * Qwik Speak Extract Command Options + * Qwik Speak Extract Options */ export interface QwikSpeakExtractOptions { /** @@ -22,10 +22,6 @@ export interface QwikSpeakExtractOptions { * Supported langs. Required */ supportedLangs: string[]; - /** - * Default lang. Required - */ - defaultLang: string; /** * Separator of nested keys. Default is '.' */ @@ -35,3 +31,8 @@ export interface QwikSpeakExtractOptions { */ keyValueSeparator?: string; } + +/** + * Translation data + */ +export type Translation = { [key: string]: any }; diff --git a/tools/inline/plugin.ts b/tools/inline/plugin.ts index b51d393..ad68391 100644 --- a/tools/inline/plugin.ts +++ b/tools/inline/plugin.ts @@ -1,10 +1,10 @@ import type { Plugin } from 'vite'; import { readFile, readdir } from 'fs/promises'; import { createWriteStream } from 'fs'; -import path from 'path'; +import { extname, normalize } from 'path'; import type { QwikSpeakInlineOptions, Translation } from './types'; -import type { Argument, Property } from '../core/parser'; +import { Argument, getTranslateAlias, parseJson, Property } from '../core/parser'; import { parseSequenceExpressions } from '../core/parser'; // Logs @@ -59,12 +59,12 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { async buildStart() { // For all langs await Promise.all(resolvedOptions.supportedLangs.map(async lang => { - const baseDir = path.normalize(`${resolvedOptions.basePath}${resolvedOptions.assetsPath}/${lang}`); + const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); // For all files const files = await readdir(baseDir); if (files.length > 0) { - const ext = path.extname(files[0]); + const ext = extname(files[0]); let data: Translation = {}; const tasks = files.map(filename => readFile(`${baseDir}/${filename}`, 'utf8')); @@ -74,7 +74,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (source) { switch (ext) { case '.json': - data = await parseJson(data, source); + data = parseJson(data, source); break; } } @@ -117,17 +117,12 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { return plugin; } -export async function parseJson(target: Translation, source: string): Promise { - target = { ...target, ...JSON.parse(source) }; - return target; -} - export function inline( code: string, translation: Translation, opts: Required ): string | null { - const alias = getAlias(code); + const alias = getTranslateAlias(code); // Parse sequence const sequence = parseSequenceExpressions(code, alias); @@ -219,13 +214,6 @@ export function inline( return code; } -export function getAlias(code: string): string { - let translateAlias = code.match(/(?<=\$translate as).*?(?=,|\})/s)?.[0]?.trim() || '$translate'; - // Escape special characters / Assert position at a word boundary - translateAlias = translateAlias.startsWith('$') ? `\\${translateAlias}` : `\\b${translateAlias}`; - return translateAlias; -} - export function multilingual(lang: string | undefined, supportedLangs: string[]): string | undefined { if (!lang) return undefined; return supportedLangs.find(x => x === lang); diff --git a/tools/tests/inline.test.ts b/tools/tests/inline.test.ts index 5d835fa..76921cc 100644 --- a/tools/tests/inline.test.ts +++ b/tools/tests/inline.test.ts @@ -1,23 +1,7 @@ -import { getKey, getValue, qwikSpeakInline, transpileFn, getAlias, addLang } from '../inline/plugin'; +import { getKey, getValue, qwikSpeakInline, transpileFn, addLang } from '../inline/plugin'; import { inlinedCode, mockCode } from './mock'; describe('inline', () => { - test('getAlias', () => { - let alias = getAlias(`import { - $translate as t, - plural as p, - formatDate as fd, - formatNumber as fn, - relativeTime as rt, - Speak, - useSpeakLocale - } from 'qwik-speak';`); - expect(alias).toBe('\\bt'); - alias = getAlias("import { $translate as t } from 'qwik-speak';"); - expect(alias).toBe('\\bt'); - alias = getAlias("import { $translate } from 'qwik-speak';"); - expect(alias).toBe('\\$translate'); - }); test('getKey', () => { let key = getKey('key1', '@@'); expect(key).toBe('key1'); diff --git a/tools/tests/parser.test.ts b/tools/tests/parser.test.ts index 2090a4a..1743a41 100644 --- a/tools/tests/parser.test.ts +++ b/tools/tests/parser.test.ts @@ -1,4 +1,4 @@ -import { parse, parseSequenceExpressions, tokenize } from '../core/parser'; +import { getTranslateAlias, parse, parseSequenceExpressions, tokenize } from '../core/parser'; describe('parser: tokenize', () => { test('tokenize', () => { @@ -362,3 +362,22 @@ describe('parser: parseSequenceExpressions', () => { ); }); }); + +describe('alias', () => { + test('getTranslateAlias', () => { + let alias = getTranslateAlias(`import { + $translate as t, + plural as p, + formatDate as fd, + formatNumber as fn, + relativeTime as rt, + Speak, + useSpeakLocale + } from 'qwik-speak';`); + expect(alias).toBe('\\bt'); + alias = getTranslateAlias("import { $translate as t } from 'qwik-speak';"); + expect(alias).toBe('\\bt'); + alias = getTranslateAlias("import { $translate } from 'qwik-speak';"); + expect(alias).toBe('\\$translate'); + }); +}); \ No newline at end of file diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 817d787..6561741 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -3,7 +3,7 @@ "target": "ES2020", "module": "ES2020", "lib": [ - "es2020" + "es2021" ], "strict": true, "declaration": true, diff --git a/tools/vite.config.extract.ts b/tools/vite.config.extract.ts index 41e948e..c025d5a 100644 --- a/tools/vite.config.extract.ts +++ b/tools/vite.config.extract.ts @@ -10,8 +10,8 @@ export default defineConfig(() => { target: 'es2020', lib: { entry: ['tools/extract/index.ts', 'tools/extract/cli.ts'], - formats: ['es', 'cjs'], - fileName: (format, entryName) => `${entryName}.${format === 'es' ? 'mjs' : 'cjs'}`, + formats: ['es'], + fileName: (format, entryName) => `${entryName}.js`, }, rollupOptions: { output: { @@ -25,7 +25,7 @@ export default defineConfig(() => { plugins: [ shebang({ shebang: '#!/usr/bin/env node', - include: ['./extract/cli.mjs', './extract/cli.cjs'] + include: ['./extract/cli.js'] }) ] } From 339090fe9ce95cb170a35d3919d740a58b921332 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sat, 5 Nov 2022 14:58:19 +0100 Subject: [PATCH 3/6] Tools(extract): testing --- package.json | 2 +- tools/core/format.ts | 2 +- tools/core/merge.ts | 4 +- tools/extract/index.ts | 3 +- tools/tests/extract.test.ts | 48 +++++++++++++++++++++ tools/tests/format.test.ts | 20 +++++++++ tools/tests/merge.test.ts | 15 +++++++ tools/tests/mock.ts | 83 +++++++++++++++++++++++++++++++++++++ 8 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 tools/tests/extract.test.ts create mode 100644 tools/tests/format.test.ts create mode 100644 tools/tests/merge.test.ts diff --git a/package.json b/package.json index b2674a5..5eb231d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build.client": "vite build", "build.server": "vite build --ssr src/entry.express.tsx", "build.static": "vite build --ssr src/entry.static.tsx", - "build.types": "tsc --incremental --noEmit", + "build.types": "tsc --noEmit", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", "lint": "eslint \"src/**/*.ts*\" \"tools/**/*.ts*\"", diff --git a/tools/core/format.ts b/tools/core/format.ts index 32c973b..493e137 100644 --- a/tools/core/format.ts +++ b/tools/core/format.ts @@ -25,4 +25,4 @@ export function sortTarget(target: { [key: string]: any }) { */ function replacer(key: string, value: any) { return typeof value === 'string' ? value.replace(/\\/g, '') : value; -}; +} diff --git a/tools/core/merge.ts b/tools/core/merge.ts index 470f45d..ee5ce70 100644 --- a/tools/core/merge.ts +++ b/tools/core/merge.ts @@ -3,9 +3,9 @@ */ export function deepSet(target: { [key: string]: any }, keys: string[], val: string) { let i = 0; - let len = keys.length; + const len = keys.length; while (i < len) { - let key = keys[i++]; + const key = keys[i++]; target[key] = (i === len) ? val : typeof target[key] === 'object' ? target[key] : {}; target = target[key]; } diff --git a/tools/extract/index.ts b/tools/extract/index.ts index 07f2371..3b1faf8 100644 --- a/tools/extract/index.ts +++ b/tools/extract/index.ts @@ -146,7 +146,8 @@ export async function extract(options: QwikSpeakExtractOptions) { let data: string; switch (resolvedOptions.format) { case 'json': - data = toJsonString({ [topLevelProperty]: translation[lang][topLevelProperty] }); // computed property name + // Computed property name + data = toJsonString({ [topLevelProperty]: translation[lang][topLevelProperty] }); break; } const file = normalize(`${baseAssets}/${topLevelProperty}.${resolvedOptions.format}`); diff --git a/tools/tests/extract.test.ts b/tools/tests/extract.test.ts new file mode 100644 index 0000000..8d229d8 --- /dev/null +++ b/tools/tests/extract.test.ts @@ -0,0 +1,48 @@ +import fs from 'fs/promises'; +import { normalize } from 'path'; + +import { extract } from '../extract/index'; +import { mockAsset, mockSource } from './mock'; + +jest.mock('fs/promises'); + +describe('extract', () => { + beforeEach(() => { + // Reset mocks + jest.resetAllMocks(); + }); + + test('extract json', async () => { + fs.readdir = jest.fn() + .mockImplementationOnce(() => [{ name: 'home.tsx', isDirectory: () => false }]) + .mockImplementationOnce(() => ['home.json']); + fs.readFile = jest.fn() + .mockImplementationOnce(() => mockSource) + .mockImplementationOnce(() => mockAsset); + fs.writeFile = jest.fn(); + + await extract({ + supportedLangs: ['en-US'] + }); + + expect(fs.writeFile).toHaveBeenCalledTimes(2); + expect(fs.writeFile).toHaveBeenNthCalledWith(1, normalize('public/i18n/en-US/app.json'), `{ + "app": { + "subtitle": "Translate your Qwik apps into any language", + "title": "Qwik Speak" + } +}`); + expect(fs.writeFile).toHaveBeenNthCalledWith(2, normalize('public/i18n/en-US/home.json'), `{ + "home": { + "dates": "Dates & relative time", + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "params": "", + "plural": "Plural", + "tags": "Html tags", + "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" + } +}`); + }); +}); diff --git a/tools/tests/format.test.ts b/tools/tests/format.test.ts new file mode 100644 index 0000000..1979b17 --- /dev/null +++ b/tools/tests/format.test.ts @@ -0,0 +1,20 @@ +import { minDepth, sortTarget } from '../core/format'; + +describe('format', () => { + test('minDepth', () => { + let target = {}; + let depth = minDepth(target); + expect(depth).toBe(0); + target = { key1: { subkey1: 'Subkey1' }, key2: 'Key2' }; + depth = minDepth(target); + expect(depth).toBe(1); + target = { key1: { subkey1: 'Subkey1' }, key2: { subkey2: 'Subkey2' } }; + depth = minDepth(target); + expect(depth).toBe(2); + }); + test('sortTarget', () => { + let target = { b: { b: 'B', a: 'A' }, a: 'A' }; + target = sortTarget(target); + expect(target).toEqual({ a: 'A', b: { a: 'A', b: 'B' } }); + }); +}); diff --git a/tools/tests/merge.test.ts b/tools/tests/merge.test.ts new file mode 100644 index 0000000..ed07cd4 --- /dev/null +++ b/tools/tests/merge.test.ts @@ -0,0 +1,15 @@ +import { deepMerge, deepSet } from '../core/merge'; + +describe('merge', () => { + test('deepSet', () => { + const target = { key1: { subkey1: 'Subkey1' } }; + deepSet(target, ['key1', 'subkey2'], 'Subkey2'); + expect(target).toEqual({ key1: { subkey1: 'Subkey1', subkey2: 'Subkey2' } }); + }); + test('deepMerge', () => { + const target = { key1: { subkey1: 'Subkey1' } }; + const source = { key1: { subkey1: 'NewSubkey1', subkey2: 'Subkey2' } }; + deepMerge(target, source); + expect(target).toEqual({ key1: { subkey1: 'NewSubkey1', subkey2: 'Subkey2' } }); + }); +}); diff --git a/tools/tests/mock.ts b/tools/tests/mock.ts index b7505a9..42753d0 100644 --- a/tools/tests/mock.ts +++ b/tools/tests/mock.ts @@ -159,3 +159,86 @@ export const s_xJBzwgVGKaQ = ()=>{ ] }); };`; + +export const mockSource = `import { component$, useStore } from '@builder.io/qwik'; +import { DocumentHead, StaticGenerateHandler } from '@builder.io/qwik-city'; +import { + $translate as t, + plural as p, + formatDate as fd, + formatNumber as fn, + relativeTime as rt, + Speak, + useSpeakLocale +} from 'qwik-speak'; + +import { config } from '../../speak-config'; + +export const Home = component$(() => { + const units = useSpeakLocale().units!; + + const state = useStore({ count: 0 }); + + return ( + <> +

{t('app.title@@Qwik Speak')}

+

{t('app.subtitle@@Translate your Qwik apps into any language')}

+ +

{t('home.params')}

+

{t('home.greeting', { name: 'Qwik Speak' })}

+ +

{t('home.tags')}

+

+ +

{t('home.plural')}

+ +

{p(state.count, 'runtime.devs')}

+ +

{t('home.dates')}

+

{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}

+

{rt(-1, 'second')}

+ +

{t('home.numbers')}

+

{fn(1000000)}

+

{fn(1000000, { style: 'currency' })}

+

{fn(1, { style: 'unit', unit: units['length'] })}

+ + ); +}); + +export default component$(() => { + return ( + /** + * Add Home translation (only available in child components) + */ + + + + ); +}); + +export const head: DocumentHead = { + title: 'runtime.head.home.title', + meta: [{ name: 'description', content: 'runtime.head.home.description' }] +}; + +// E.g. SSG +export const onStaticGenerate: StaticGenerateHandler = () => { + return { + params: config.supportedLocales.map(locale => { + return { lang: locale.lang !== config.defaultLocale.lang ? locale.lang : '' }; + }), + }; +};`; + +export const mockAsset = JSON.stringify({ + "home": { + "dates": "Dates & relative time", + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "plural": "Plural", + "tags": "Html tags", + "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" + } +}, null, 2); From 40fb3cc89f2ebfb84519e2cbaf9ce75af59bb0cc Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Tue, 8 Nov 2022 14:48:20 +0100 Subject: [PATCH 4/6] Tools(extract): docs --- README.md | 109 ++++++++++++++---------- package.json | 2 +- src/app/routes/[...lang]/index.tsx | 2 +- src/app/routes/[...lang]/page/index.tsx | 2 +- tools/extract.md | 69 +++++++++++++++ tools/extract/cli.ts | 4 +- tools/extract/index.ts | 2 +- 7 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 tools/extract.md diff --git a/README.md b/README.md index 2da132f..789c97f 100644 --- a/README.md +++ b/README.md @@ -5,41 +5,7 @@ Live example on [StackBlitz](https://stackblitz.com/edit/qwik-speak) -## Speak context -```mermaid -stateDiagram-v2 - State1: SpeakState - State2: SpeakLocale - State3: Translation - State4: SpeakConfig - State5: TranslateFn - State1 --> State2 - State1 --> State3 - State1 --> State4 - State1 --> State5 - note right of State2 - - lang - - extension (Intl) - - currency - - timezone - - unit - end note - note right of State3 - key-value pairs - of translation data - end note - note right of State4: Configuration - note right of State5 - Custom APIs: - - loadTranslation$ - - resolveLocale$ - - storeLocale$ - - handleMissingTranslation$ - end note -``` - ## Usage -### Getting started ```shell npm install qwik-speak --save-dev ``` @@ -50,8 +16,9 @@ import { $translate as t, plural as p } from 'qwik-speak'; export default component$(() => { return ( <> -

{t('app.title', { name: 'Qwik Speak' })}

{/* I'm Qwik Speak */} -

{p(1, 'app.devs')}

{/* 1 software developer */} +

{t('app.title')}

{/* Qwik Speak */} +

{t('home.greeting', { name: 'Qwik Speak' })}

{/* Hi! I am Qwik Speak */} +

{p(state.count, 'runtime.devs')}

{/* 1 software developer, 2 software developers */} ); }); @@ -64,7 +31,7 @@ export default component$(() => { return ( <>

{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}

{/* Wednesday, July 20, 2022 at 7:09 AM */} -

{rt(-1, 'day')}

{/* 1 day ago */} +

{rt(-1, 'second')}

{/* 1 second ago */}

{fn(1000000, { style: 'currency' })}

{/* $1,000,000.00 */} ); @@ -85,19 +52,16 @@ export const config: SpeakConfig = { ] }; ``` -Assets will be loaded through the implementation of `loadTranslation$` function below. You can load _json_ files or call an _endpoint_ to return a `Translation` object for each language: +Assets will be loaded through the implementation of `loadTranslation$` function below. You can load _json_ files or call an _endpoint_ to return a `Translation` object of key-value pairs for each language: + ```json { "app": { - "title": "I'm {{name}}", - "devs": { - "one": "{{value}} software developer", - "other": "{{value}} software developers" - } + "title": "Qwik Speak" } } ``` -### Custom APIs +#### Custom APIs ```typescript import { $ } from '@builder.io/qwik'; @@ -154,6 +118,18 @@ export default component$(() => { }); ``` ### Lazy loading of translation data +```mermaid +C4Container + Container_Boundary(a, "App") { + Component(a0, "QwikSpeak", "", "Uses Speak context") + Container_Boundary(b1, "Home") { + Component(a10, "Speak", "", "Adds its own translation data to the context") + } + Container_Boundary(b2, "Page") { + Component(a20, "Speak", "", "Adds its own translation data to the context") + } + } +``` Create a different translation data file (asset) for each page and use `Speak` component to add translation data to the context: ```jsx import { Speak } from 'qwik-speak'; @@ -161,7 +137,7 @@ import { Speak } from 'qwik-speak'; export default component$(() => { return ( /** - * Add Home translation (only available in child components) + * Add Home translations (only available in child components) */ @@ -183,6 +159,11 @@ export default component$(() => { ``` The translation data of the additional languages are preloaded along with the current language. They can be used as a fallback for missing values by implementing `handleMissingTranslation$`, or for multilingual pages. +## Extraction of translations +To extract translations directly from the components, a command is available that automatically generates the files with the keys and default values. + +See [Qwik Speak Extract](./tools/extract.md) for more information on how to use it. + ## Production You have three solutions: - **Build as is** Translation happens _at runtime_: translations are loaded during SSR or on client, and the lookup also happens at runtime as in development mode @@ -226,7 +207,7 @@ Separator of nested keys. Default is `.` - `keyValueSeparator` Key-value separator. Default is `@@` - The default value of a key can be passed directly into the string: `t("app.title@@I'm {{name}}")` + The default value of a key can be passed directly into the string: `t('app.title@@Qwik Speak')` The `SpeakLocale` object contains the `lang`, in the format `language[-script][-region]`, where: - `language`: ISO 639 two-letter or three-letter code @@ -239,6 +220,39 @@ and optionally contains: - `timezone` From the IANA time zone database - `units` Key value pairs of unit identifiers +## Speak context +```mermaid +stateDiagram-v2 + State1: SpeakState + State2: SpeakLocale + State3: Translation + State4: SpeakConfig + State5: TranslateFn + State1 --> State2 + State1 --> State3 + State1 --> State4 + State1 --> State5 + note right of State2 + - lang + - extension (Intl) + - currency + - timezone + - unit + end note + note right of State3 + key-value pairs + of translation data + end note + note right of State4: Configuration + note right of State5 + Custom APIs: + - loadTranslation$ + - resolveLocale$ + - storeLocale$ + - handleMissingTranslation$ + end note +``` + ## APIs ### Functions - `$translate(keys: string | string[], params?: any, ctx?: SpeakState, lang?: string)` @@ -305,8 +319,9 @@ npm run serve.ssg ``` ## What's new -> Released v0.1.0 +> Released v0.2.0 +- Extract translations: [Qwik Speak Extract](./tools/extract.md) - Inline translation data at compile time: [Qwik Speak Inline Vite plugin](./tools/inline.md) ## License diff --git a/package.json b/package.json index f224b2c..ff9083b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build.app": "qwik build", "build.client": "vite build", "build.server": "vite build -c adaptors/express/vite.config.ts", - "build.types": "tsc --incremental --noEmit", + "build.types": "tsc --noEmit", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", "lint": "eslint src/**/*.ts* tools/**/*.ts*", diff --git a/src/app/routes/[...lang]/index.tsx b/src/app/routes/[...lang]/index.tsx index 0d7d6b2..54258d0 100644 --- a/src/app/routes/[...lang]/index.tsx +++ b/src/app/routes/[...lang]/index.tsx @@ -47,7 +47,7 @@ export const Home = component$(() => { export default component$(() => { return ( /** - * Add Home translation (only available in child components) + * Add Home translations (only available in child components) */ diff --git a/src/app/routes/[...lang]/page/index.tsx b/src/app/routes/[...lang]/page/index.tsx index 2ac1894..17d48db 100644 --- a/src/app/routes/[...lang]/page/index.tsx +++ b/src/app/routes/[...lang]/page/index.tsx @@ -18,7 +18,7 @@ export const Page = component$(() => { export default component$(() => { return ( /** - * Add Page translation (only available in child components) + * Add Page translations (only available in child components) */ diff --git a/tools/extract.md b/tools/extract.md new file mode 100644 index 0000000..164dc52 --- /dev/null +++ b/tools/extract.md @@ -0,0 +1,69 @@ +# Qwik Speak Extract + +> Extract translations directly from the components + +## Usage +### Command +#### Get the code ready +Optionally, you can use a default value for the keys. The syntax is `key@@[default value]`: +```html +

{t('app.title@@Qwik Speak'}

+

{t('home.greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' })}

+ +``` +When you use a default value, it will be used as initial value for the key in every translation. + +> Note. A key will not be extracted when a function argument is a variable (dynamic). + +#### Naming conventions +If you use nested keys _in all app keys_, the first property will be used as filename: +```html +

{t('app.text)}

+

{t('home.greeting)}

+``` +will generate two files for each language: +``` +public/i18n +│ +└───en-US + app.json + home.json +``` +But if you use some non-nested key, only one file for each language will be generated, called `app.json` + +#### Configuration +Add the command in `package.json`, and provide at least the supported languages: +```json +"scripts": { + ... + "qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT" +} +``` +Available options: +- `basePath` The base path. Default to `'./'` +- `sourceFilesPath` Path to files to search for translations. Default to `'src'` +- `assetsPath` Path to translation files: `[basePath]/[assetsPath]/[lang]/*.json`. Default to `'public/i18n'` +- `format` The format of the translation files. Default to `'json'` +- `supportedLangs` Supported langs. Required +- `keySeparator` Separator of nested keys. Default is `'.'` +- `keyValueSeparator` Key-value separator. Default is `'@@'` + +> Note. Currently, only `json` files are supported as assets + +#### Running +```shell +npm run qwik-speak-extract +``` + +#### Updating +If you add new translations in the components, or a new language, they will be merged into the existing files without losing the translations already made. + +### Using it programmatically +Rather than using the command, you can invoke `qwikSpeakExtract` function: +```typescript +import { qwikSpeakExtract } from 'qwik-speak/extract'; + +await qwikSpeakExtract({ + supportedLangs: ['en-US', 'it-IT'] +}); +``` diff --git a/tools/extract/cli.ts b/tools/extract/cli.ts index 10a3571..c6d2042 100644 --- a/tools/extract/cli.ts +++ b/tools/extract/cli.ts @@ -1,6 +1,6 @@ import type { QwikSpeakExtractOptions } from './types'; import { parseArgument } from '../core/cli-parser'; -import { extract } from './index'; +import { qwikSpeakExtract } from './index'; const assertType = (value: any, type: string): boolean => { if (type === value) return true; @@ -75,4 +75,4 @@ if (errors.length > 0) { console.log('\x1b[36m%s\x1b[0m', 'Qwik Speak Extract'); console.log('\x1b[32m%s\x1b[0m', 'extracting translation...'); -extract(options as QwikSpeakExtractOptions); +qwikSpeakExtract(options as QwikSpeakExtractOptions); diff --git a/tools/extract/index.ts b/tools/extract/index.ts index 3b1faf8..5f15e3e 100644 --- a/tools/extract/index.ts +++ b/tools/extract/index.ts @@ -10,7 +10,7 @@ import { minDepth, sortTarget, toJsonString } from '../core/format'; /** * Extract translations from source files */ -export async function extract(options: QwikSpeakExtractOptions) { +export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { // Resolve options const resolvedOptions: Required = { ...options, From fe863af5d684ee3e70be56152a05fecd091eece8 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Thu, 10 Nov 2022 17:05:15 +0100 Subject: [PATCH 5/6] Tools(extract): fixes --- README.md | 79 ++++++++++++++++++------------------- package-lock.json | 28 ++++++------- package.json | 4 +- src/app/speak-config.ts | 3 +- tools/extract.md | 3 +- tools/inline.md | 2 +- tools/tests/extract.test.ts | 4 +- 7 files changed, 60 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 789c97f..becf156 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ export default component$(() => { ); }); ``` -### Lazy loading of translation data +### Scoped translation data ```mermaid C4Container Container_Boundary(a, "App") { @@ -159,22 +159,7 @@ export default component$(() => { ``` The translation data of the additional languages are preloaded along with the current language. They can be used as a fallback for missing values by implementing `handleMissingTranslation$`, or for multilingual pages. -## Extraction of translations -To extract translations directly from the components, a command is available that automatically generates the files with the keys and default values. - -See [Qwik Speak Extract](./tools/extract.md) for more information on how to use it. - -## Production -You have three solutions: -- **Build as is** Translation happens _at runtime_: translations are loaded during SSR or on client, and the lookup also happens at runtime as in development mode -- **Build using Qwik Speak Inline Vite plugin** Translation happens _at compile-time_: translations are loaded and inlined during the buid (both in server file and in chunks sent to the browser) -- **Build using Qwik Speak Inline Vite plugin & runtime** Translation happens _at compile-time_ or _at runtime_ as needed: static translations are loaded and inlined during the buid, while dynamic translations occur at runtime - -See [Qwik Speak Inline Vite plugin](./tools/inline.md) for more information on how it works and how to use it. - -### Static Site Generation (SSG) -Using SSG offered by Qwik City, you can prerender the pages for each language. - +### Localized routing What you need: - A `lang` parameter in the root, like: ``` @@ -187,9 +172,21 @@ What you need: index.html ``` - Handle the localized routing in `resolveLocale$` and `storeLocale$` -- Qwik City Static Site Generation config and dynamic routes -The [sample app](./src/app) in this project uses _Qwik Speak Inline Vite plugin & runtime_ solution and implements SSG. +## Extraction of translations +To extract translations directly from the components, a command is available that automatically generates the files with the keys and default values. + +See [Qwik Speak Extract](./tools/extract.md) for more information on how to use it. + +## Production +You have three solutions: +- **Build as is** Translation happens _at runtime_: translations are loaded during SSR or on client, and the lookup also happens at runtime as in development mode +- **Build using Qwik Speak Inline Vite plugin** Translation happens _at compile-time_: translations are loaded and inlined during the build (both in server file and in chunks sent to the browser) +- **Build using Qwik Speak Inline Vite plugin & runtime** Translation happens _at compile-time_ or _at runtime_ as needed: static translations are loaded and inlined during the build, while dynamic translations occur at runtime + +See [Qwik Speak Inline Vite plugin](./tools/inline.md) for more information on how it works and how to use it. + +The [sample app](./src/app) in this project uses a localized routing, _Qwik Speak Inline Vite plugin & runtime_ solution and implements SSG. ## Speak config - `defaultLocale` @@ -220,7 +217,27 @@ and optionally contains: - `timezone` From the IANA time zone database - `units` Key value pairs of unit identifiers -## Speak context +## APIs +### Functions +- `$translate(keys: string | string[], params?: any, ctx?: SpeakState, lang?: string)` +Translates a key or an array of keys. The syntax of the string is `key@@[default value]` + +- `plural(value: number | string, prefix?: string, options?: Intl.PluralRulesOptions, ctx?: SpeakState, lang?: string)` +Gets the plural by a number + +- `formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions, locale?: SpeakLocale, lang?: string, timeZone?: string)` +Formats a date + +- `relativeTime(value: number | string, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions, locale?: SpeakLocale, lang?: string)` +Format a relative time + +- `formatNumber(value: number | string, options?: Intl.NumberFormatOptions, locale?: SpeakLocale, lang?: string, currency?: string)` +Formats a number + +- `changeLocale(newLocale: SpeakLocale, ctx: SpeakState)` +Changes locale at runtime: loads translation data and rerenders components that uses translations + +### Speak context ```mermaid stateDiagram-v2 State1: SpeakState @@ -253,28 +270,8 @@ stateDiagram-v2 end note ``` -## APIs -### Functions -- `$translate(keys: string | string[], params?: any, ctx?: SpeakState, lang?: string)` -Translates a key or an array of keys. The syntax of the string is `key@@[default value]` - -- `plural(value: number | string, prefix?: string, options?: Intl.PluralRulesOptions, ctx?: SpeakState, lang?: string)` -Gets the plural by a number - -- `formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions, locale?: SpeakLocale, lang?: string, timeZone?: string)` -Formats a date - -- `relativeTime(value: number | string, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions, locale?: SpeakLocale, lang?: string)` -Format a relative time - -- `formatNumber(value: number | string, options?: Intl.NumberFormatOptions, locale?: SpeakLocale, lang?: string, currency?: string)` -Formats a number - -- `changeLocale(newLocale: SpeakLocale, ctx: SpeakState)` -Changes locale at runtime: loads translation data and rerenders components that uses translations -### Speak context - `useSpeakContext()` -Returns the Speak context +Returns the Speak state - `useSpeakLocale()` Returns the locale in Speak context diff --git a/package-lock.json b/package-lock.json index e930134..28dfdc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "qwik-speak-extract": "extract/cli.js" }, "devDependencies": { - "@builder.io/qwik": "0.12.1", - "@builder.io/qwik-city": "0.0.118", + "@builder.io/qwik": "0.13.0-dev20221108030156", + "@builder.io/qwik-city": "0.0.119", "@microsoft/api-extractor": "^7.32.0", "@playwright/test": "^1.24.0", "@types/compression": "^1.7.2", @@ -649,9 +649,9 @@ "dev": true }, "node_modules/@builder.io/qwik": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.12.1.tgz", - "integrity": "sha512-ePxm9tr6qa+cNySuIhXOElozkkqdRfyPvEPEV659QJSq704C031/VC0S2lqqvKsc9+ZogBlejGuYWLmtRrsT6w==", + "version": "0.13.0-dev20221108030156", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.0-dev20221108030156.tgz", + "integrity": "sha512-rYXx6KFpslkpMFopt5fwVGUjTA3sC6qCbVl+tnfDpba3B+DydWoEOqP2Sjiy31Mna+krb+NmW56iCmdsRhv8bg==", "dev": true, "bin": { "qwik": "qwik.cjs" @@ -661,9 +661,9 @@ } }, "node_modules/@builder.io/qwik-city": { - "version": "0.0.118", - "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.118.tgz", - "integrity": "sha512-vkc20xK3HUr6rviVpDeOwbzCfo6QTjZWBL9oyGYcYLfqqC1gvvuc+zf548APAwbTQa+4/7pzJCNMVixvDyD/4g==", + "version": "0.0.119", + "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.119.tgz", + "integrity": "sha512-bQQcr3BDbJvegyAnosdN5xFAHgg2yQw2lmqiETG1gGqNh/WQo9OD+N+e98lhNJRzw37WtRpq6bz2/+JD710BFA==", "dev": true, "dependencies": { "@mdx-js/mdx": "2.1.5", @@ -12222,15 +12222,15 @@ "dev": true }, "@builder.io/qwik": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.12.1.tgz", - "integrity": "sha512-ePxm9tr6qa+cNySuIhXOElozkkqdRfyPvEPEV659QJSq704C031/VC0S2lqqvKsc9+ZogBlejGuYWLmtRrsT6w==", + "version": "0.13.0-dev20221108030156", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.0-dev20221108030156.tgz", + "integrity": "sha512-rYXx6KFpslkpMFopt5fwVGUjTA3sC6qCbVl+tnfDpba3B+DydWoEOqP2Sjiy31Mna+krb+NmW56iCmdsRhv8bg==", "dev": true }, "@builder.io/qwik-city": { - "version": "0.0.118", - "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.118.tgz", - "integrity": "sha512-vkc20xK3HUr6rviVpDeOwbzCfo6QTjZWBL9oyGYcYLfqqC1gvvuc+zf548APAwbTQa+4/7pzJCNMVixvDyD/4g==", + "version": "0.0.119", + "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.119.tgz", + "integrity": "sha512-bQQcr3BDbJvegyAnosdN5xFAHgg2yQw2lmqiETG1gGqNh/WQo9OD+N+e98lhNJRzw37WtRpq6bz2/+JD710BFA==", "dev": true, "requires": { "@mdx-js/mdx": "2.1.5", diff --git a/package.json b/package.json index ff9083b..ddec4a5 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "@builder.io/qwik": ">=0.12.1" }, "devDependencies": { - "@builder.io/qwik": "0.12.1", - "@builder.io/qwik-city": "0.0.118", + "@builder.io/qwik": "0.13.0-dev20221108030156", + "@builder.io/qwik-city": "0.0.119", "@microsoft/api-extractor": "^7.32.0", "@playwright/test": "^1.24.0", "@types/compression": "^1.7.2", diff --git a/src/app/speak-config.ts b/src/app/speak-config.ts index 646ac8d..7f13b81 100644 --- a/src/app/speak-config.ts +++ b/src/app/speak-config.ts @@ -76,7 +76,8 @@ export const storeLocale$: StoreLocaleFn = $((locale: SpeakLocale) => { url.pathname = `/${locale.lang}${url.pathname}`; } - window.history.pushState({}, '', url); + // E.g. Just replace the state: no back or forward on language change + window.history.replaceState({}, '', url); } }); diff --git a/tools/extract.md b/tools/extract.md index 164dc52..af11bd1 100644 --- a/tools/extract.md +++ b/tools/extract.md @@ -35,7 +35,6 @@ But if you use some non-nested key, only one file for each language will be gene Add the command in `package.json`, and provide at least the supported languages: ```json "scripts": { - ... "qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT" } ``` @@ -48,7 +47,7 @@ Available options: - `keySeparator` Separator of nested keys. Default is `'.'` - `keyValueSeparator` Key-value separator. Default is `'@@'` -> Note. Currently, only `json` files are supported as assets +> Note. Currently, only `json` is supported as format #### Running ```shell diff --git a/tools/inline.md b/tools/inline.md index bbede58..74b32e2 100644 --- a/tools/inline.md +++ b/tools/inline.md @@ -25,7 +25,7 @@ export default defineConfig(() => { qwikVite(), qwikSpeakInline({ basePath: './', - assetsPath: './public/i18n', + assetsPath: 'public/i18n', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US' }), diff --git a/tools/tests/extract.test.ts b/tools/tests/extract.test.ts index 8d229d8..b32831b 100644 --- a/tools/tests/extract.test.ts +++ b/tools/tests/extract.test.ts @@ -1,7 +1,7 @@ import fs from 'fs/promises'; import { normalize } from 'path'; -import { extract } from '../extract/index'; +import { qwikSpeakExtract } from '../extract/index'; import { mockAsset, mockSource } from './mock'; jest.mock('fs/promises'); @@ -21,7 +21,7 @@ describe('extract', () => { .mockImplementationOnce(() => mockAsset); fs.writeFile = jest.fn(); - await extract({ + await qwikSpeakExtract({ supportedLangs: ['en-US'] }); From f50f80dcfc3ff794e8c9c6d87b16d2ba226072bc Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sat, 12 Nov 2022 17:40:13 +0100 Subject: [PATCH 6/6] Update Qwik --- README.md | 2 +- adaptors/express/vite.config.ts | 3 - package-lock.json | 236 +++++++++++++++++--------------- package.json | 14 +- tools/extract.md | 4 +- tools/inline.md | 2 +- 6 files changed, 136 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index becf156..ff4b5a5 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ export default component$(() => { ); }); ``` -### Scoped translation data +### Scoped translations ```mermaid C4Container Container_Boundary(a, "App") { diff --git a/adaptors/express/vite.config.ts b/adaptors/express/vite.config.ts index b08fb39..e09c790 100644 --- a/adaptors/express/vite.config.ts +++ b/adaptors/express/vite.config.ts @@ -11,9 +11,6 @@ export default extendConfig(baseConfig, () => { }, }, plugins: [ - expressAdaptor({ - staticGenerate: true, - }), ], }; }); diff --git a/package-lock.json b/package-lock.json index 28dfdc1..2fee357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,17 +12,17 @@ "qwik-speak-extract": "extract/cli.js" }, "devDependencies": { - "@builder.io/qwik": "0.13.0-dev20221108030156", - "@builder.io/qwik-city": "0.0.119", + "@builder.io/qwik": "0.13.3", + "@builder.io/qwik-city": "0.0.122", "@microsoft/api-extractor": "^7.32.0", "@playwright/test": "^1.24.0", "@types/compression": "^1.7.2", - "@types/eslint": "8.4.8", + "@types/eslint": "8.4.9", "@types/express": "4.17.13", "@types/jest": "latest", "@types/node": "latest", - "@typescript-eslint/eslint-plugin": "5.41.0", - "@typescript-eslint/parser": "5.41.0", + "@typescript-eslint/eslint-plugin": "5.42.0", + "@typescript-eslint/parser": "5.42.0", "compression": "^1.7.4", "eslint": "8.26.0", "eslint-plugin-qwik": "latest", @@ -33,14 +33,14 @@ "rollup-plugin-add-shebang": "^0.3.1", "ts-jest": "^29.0.3", "typescript": "4.8.4", - "vite": "3.2.0", + "vite": "3.2.2", "vite-tsconfig-paths": "3.5.0" }, "engines": { "node": ">=16" }, "peerDependencies": { - "@builder.io/qwik": ">=0.12.1" + "@builder.io/qwik": ">=0.13.3" } }, "node_modules/@ampproject/remapping": { @@ -649,9 +649,9 @@ "dev": true }, "node_modules/@builder.io/qwik": { - "version": "0.13.0-dev20221108030156", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.0-dev20221108030156.tgz", - "integrity": "sha512-rYXx6KFpslkpMFopt5fwVGUjTA3sC6qCbVl+tnfDpba3B+DydWoEOqP2Sjiy31Mna+krb+NmW56iCmdsRhv8bg==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.3.tgz", + "integrity": "sha512-revtQDBww5lYM6oEsmdRk0n+Z38sFghxjbpyNZFjN3h20FHoym7fXwxSUag+0wdeU9c3CRWDKoLJFtnngcLfCg==", "dev": true, "bin": { "qwik": "qwik.cjs" @@ -661,9 +661,9 @@ } }, "node_modules/@builder.io/qwik-city": { - "version": "0.0.119", - "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.119.tgz", - "integrity": "sha512-bQQcr3BDbJvegyAnosdN5xFAHgg2yQw2lmqiETG1gGqNh/WQo9OD+N+e98lhNJRzw37WtRpq6bz2/+JD710BFA==", + "version": "0.0.122", + "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.122.tgz", + "integrity": "sha512-p1fMVvzREKjEnq2Mf2F4D272OaguA4uSj9qxmCXHZmvG5T355QFRGlq6tTgC7nJAqwlTAB8thhB/3kykfBO/Ng==", "dev": true, "dependencies": { "@mdx-js/mdx": "2.1.5", @@ -1648,9 +1648,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.4.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.8.tgz", - "integrity": "sha512-zUCKQI1bUCTi+0kQs5ZQzQ/XILWRLIlh15FXWNykJ+NG3TMKMVvwwC6GP3DR1Ylga15fB7iAExSzc4PNlR5i3w==", + "version": "8.4.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", + "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", "dev": true, "dependencies": { "@types/estree": "*", @@ -1896,16 +1896,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz", + "integrity": "sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/type-utils": "5.42.0", + "@typescript-eslint/utils": "5.42.0", "debug": "^4.3.4", "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" @@ -1928,14 +1929,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.0.tgz", + "integrity": "sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/typescript-estree": "5.42.0", "debug": "^4.3.4" }, "engines": { @@ -1955,13 +1956,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz", + "integrity": "sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/visitor-keys": "5.42.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1972,13 +1973,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz", + "integrity": "sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", + "@typescript-eslint/typescript-estree": "5.42.0", + "@typescript-eslint/utils": "5.42.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -1999,9 +2000,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.0.tgz", + "integrity": "sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2012,13 +2013,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz", + "integrity": "sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/visitor-keys": "5.42.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2039,16 +2040,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.42.0.tgz", + "integrity": "sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/typescript-estree": "5.42.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -2065,12 +2066,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz", + "integrity": "sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/types": "5.42.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -8621,6 +8622,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -11462,9 +11469,9 @@ } }, "node_modules/vite": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.0.tgz", - "integrity": "sha512-Ovj7+cqIdM1I0LPCk2CWxzgADXMix3NLXpUT6g7P7zg/a9grk/TaC3qn9YMg7w7M0POIVCBOp1aBANJW+RH7oA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz", + "integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==", "dev": true, "dependencies": { "esbuild": "^0.15.9", @@ -12222,15 +12229,15 @@ "dev": true }, "@builder.io/qwik": { - "version": "0.13.0-dev20221108030156", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.0-dev20221108030156.tgz", - "integrity": "sha512-rYXx6KFpslkpMFopt5fwVGUjTA3sC6qCbVl+tnfDpba3B+DydWoEOqP2Sjiy31Mna+krb+NmW56iCmdsRhv8bg==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-0.13.3.tgz", + "integrity": "sha512-revtQDBww5lYM6oEsmdRk0n+Z38sFghxjbpyNZFjN3h20FHoym7fXwxSUag+0wdeU9c3CRWDKoLJFtnngcLfCg==", "dev": true }, "@builder.io/qwik-city": { - "version": "0.0.119", - "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.119.tgz", - "integrity": "sha512-bQQcr3BDbJvegyAnosdN5xFAHgg2yQw2lmqiETG1gGqNh/WQo9OD+N+e98lhNJRzw37WtRpq6bz2/+JD710BFA==", + "version": "0.0.122", + "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-0.0.122.tgz", + "integrity": "sha512-p1fMVvzREKjEnq2Mf2F4D272OaguA4uSj9qxmCXHZmvG5T355QFRGlq6tTgC7nJAqwlTAB8thhB/3kykfBO/Ng==", "dev": true, "requires": { "@mdx-js/mdx": "2.1.5", @@ -13041,9 +13048,9 @@ } }, "@types/eslint": { - "version": "8.4.8", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.8.tgz", - "integrity": "sha512-zUCKQI1bUCTi+0kQs5ZQzQ/XILWRLIlh15FXWNykJ+NG3TMKMVvwwC6GP3DR1Ylga15fB7iAExSzc4PNlR5i3w==", + "version": "8.4.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", + "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", "dev": true, "requires": { "@types/estree": "*", @@ -13289,69 +13296,70 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", - "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz", + "integrity": "sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/type-utils": "5.41.0", - "@typescript-eslint/utils": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/type-utils": "5.42.0", + "@typescript-eslint/utils": "5.42.0", "debug": "^4.3.4", "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" } }, "@typescript-eslint/parser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", - "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.0.tgz", + "integrity": "sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/typescript-estree": "5.42.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", - "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz", + "integrity": "sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0" + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/visitor-keys": "5.42.0" } }, "@typescript-eslint/type-utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", - "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz", + "integrity": "sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.41.0", - "@typescript-eslint/utils": "5.41.0", + "@typescript-eslint/typescript-estree": "5.42.0", + "@typescript-eslint/utils": "5.42.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", - "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.0.tgz", + "integrity": "sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", - "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz", + "integrity": "sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/visitor-keys": "5.41.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/visitor-keys": "5.42.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -13360,28 +13368,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", - "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.42.0.tgz", + "integrity": "sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.41.0", - "@typescript-eslint/types": "5.41.0", - "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/scope-manager": "5.42.0", + "@typescript-eslint/types": "5.42.0", + "@typescript-eslint/typescript-estree": "5.42.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", - "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz", + "integrity": "sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/types": "5.42.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -18083,6 +18091,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -20159,9 +20173,9 @@ } }, "vite": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.0.tgz", - "integrity": "sha512-Ovj7+cqIdM1I0LPCk2CWxzgADXMix3NLXpUT6g7P7zg/a9grk/TaC3qn9YMg7w7M0POIVCBOp1aBANJW+RH7oA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.2.tgz", + "integrity": "sha512-pLrhatFFOWO9kS19bQ658CnRYzv0WLbsPih6R+iFeEEhDOuYgYCX2rztUViMz/uy/V8cLCJvLFeiOK7RJEzHcw==", "dev": true, "requires": { "esbuild": "^0.15.9", diff --git a/package.json b/package.json index ddec4a5..9410add 100644 --- a/package.json +++ b/package.json @@ -27,20 +27,20 @@ "qwik-speak-extract": "./extract/cli.js" }, "peerDependencies": { - "@builder.io/qwik": ">=0.12.1" + "@builder.io/qwik": ">=0.13.3" }, "devDependencies": { - "@builder.io/qwik": "0.13.0-dev20221108030156", - "@builder.io/qwik-city": "0.0.119", + "@builder.io/qwik": "0.13.3", + "@builder.io/qwik-city": "0.0.122", "@microsoft/api-extractor": "^7.32.0", "@playwright/test": "^1.24.0", "@types/compression": "^1.7.2", - "@types/eslint": "8.4.8", + "@types/eslint": "8.4.9", "@types/express": "4.17.13", "@types/jest": "latest", "@types/node": "latest", - "@typescript-eslint/eslint-plugin": "5.41.0", - "@typescript-eslint/parser": "5.41.0", + "@typescript-eslint/eslint-plugin": "5.42.0", + "@typescript-eslint/parser": "5.42.0", "compression": "^1.7.4", "eslint": "8.26.0", "eslint-plugin-qwik": "latest", @@ -51,7 +51,7 @@ "ts-jest": "^29.0.3", "typescript": "4.8.4", "rollup-plugin-add-shebang": "^0.3.1", - "vite": "3.2.0", + "vite": "3.2.2", "vite-tsconfig-paths": "3.5.0" }, "engines": { diff --git a/tools/extract.md b/tools/extract.md index af11bd1..343e424 100644 --- a/tools/extract.md +++ b/tools/extract.md @@ -16,7 +16,7 @@ When you use a default value, it will be used as initial value for the key in ev > Note. A key will not be extracted when a function argument is a variable (dynamic). #### Naming conventions -If you use nested keys _in all app keys_, the first property will be used as filename: +If you use scoped translations, the first property will be used as filename: ```html

{t('app.text)}

{t('home.greeting)}

@@ -29,7 +29,7 @@ public/i18n app.json home.json ``` -But if you use some non-nested key, only one file for each language will be generated, called `app.json` +But if you don't always use scoped translations, only one file for each language will be generated, called `app.json` #### Configuration Add the command in `package.json`, and provide at least the supported languages: diff --git a/tools/inline.md b/tools/inline.md index 74b32e2..e753178 100644 --- a/tools/inline.md +++ b/tools/inline.md @@ -63,7 +63,7 @@ When there are translations with dynamic keys or params, you can manage them at } }); ``` -Likewise, you can also create lazy loaded runtime files for the different pages. +Likewise, you can also create scoped runtime files for the different pages. > Note. The `plural` function must be handled as a dynamic translation