diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..785e82b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +npm test +npx lint-staged \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c97c5cf --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +node_modules +husky +coverage +dist +.github +assets +*.md +LICENSE +tsconfig.* +package* \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6eb6342 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": false, + "trailingComma": "none", + "singleQuote": true, + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "endOfLine": "lf" +} diff --git a/jest.config.ts b/jest.config.ts index 92b6921..bf676cd 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,11 +1,23 @@ -import { type JestConfigWithTsJest } from 'ts-jest' +import { type JestConfigWithTsJest } from 'ts-jest' import { defaults as tsjPreset } from 'ts-jest/presets' const config: JestConfigWithTsJest = { verbose: true, transform: { - ...tsjPreset.transform, - } -}; + ...tsjPreset.transform + }, + collectCoverage: true, + coverageDirectory: 'coverage', + coverageProvider: 'v8', + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100 + } + }, + testEnvironment: 'node' +} -export default config \ No newline at end of file +export default config diff --git a/package-lock.json b/package-lock.json index 8f809ab..b3538d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@marchintosh94/i18n-pro", - "version": "0.1.4", + "version": "0.1.5", "lockfileVersion": 2, "requires": true, "packages": { @@ -11,8 +11,11 @@ "devDependencies": { "@types/jest": "^29.5.11", "@types/node": "^20.11.0", + "husky": "^9.0.11", "jest": "^29.7.0", "json": "^11.0.0", + "lint-staged": "^15.2.2", + "prettier": "^3.2.5", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", "ts-utils": "^6.1.0", @@ -1641,6 +1644,87 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1689,6 +1773,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -1894,6 +1984,12 @@ "node": ">=4" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -2051,6 +2147,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2175,6 +2283,21 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "dev": true, + "bin": { + "husky": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3090,30 +3213,473 @@ "node": ">=6" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", + "dev": true, + "dependencies": { + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3446,6 +4012,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -3476,6 +4054,21 @@ "queue-lit": "^1.5.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -3631,6 +4224,22 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3641,6 +4250,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3715,6 +4330,46 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3752,6 +4407,15 @@ "node": ">=10" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4200,6 +4864,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -5505,6 +6178,59 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "requires": { + "restore-cursor": "^4.0.0" + } + }, + "cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5543,6 +6269,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -5687,6 +6419,12 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5810,6 +6548,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -5898,6 +6642,12 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "dev": true + }, "ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -6586,12 +7336,192 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true }, + "lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "lint-staged": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", + "integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", + "dev": true, + "requires": { + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.1", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "dependencies": { + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + } + } + }, + "listr2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", + "integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", + "dev": true, + "requires": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -6607,6 +7537,104 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dev": true, + "requires": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "requires": { + "type-fest": "^3.0.0" + } + }, + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.0.0" + } + }, + "slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + } + }, + "string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true + }, + "wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6855,6 +7883,12 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true + }, "pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -6879,6 +7913,12 @@ "queue-lit": "^1.5.0" } }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, "pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -6979,12 +8019,28 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7033,6 +8089,30 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -7064,6 +8144,12 @@ "escape-string-regexp": "^2.0.0" } }, + "string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -7359,6 +8445,12 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 4d42273..ff2cec5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@marchintosh94/i18n-pro", - "version": "0.1.4", + "version": "0.1.5", "description": "Translation library i18n (TS)", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -12,7 +12,8 @@ "compile": "tsc -p ./tsconfig.build.json", "build": "npm run clean && npm run compile", "test": "jest", - "coverage": "jest --coverage" + "coverage": "jest --coverage", + "prepare": "husky" }, "repository": { "type": "git", @@ -38,11 +39,19 @@ "devDependencies": { "@types/jest": "^29.5.11", "@types/node": "^20.11.0", + "husky": "^9.0.11", "jest": "^29.7.0", "json": "^11.0.0", + "lint-staged": "^15.2.2", + "prettier": "^3.2.5", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", "ts-utils": "^6.1.0", "tsc-alias": "^1.8.7" + }, + "lint-staged": { + "*.{js,ts,tsx}": [ + "prettier --write" + ] } } diff --git a/src/i18npro/i18npro.ts b/src/i18npro/i18npro.ts index 6800429..7b377c8 100644 --- a/src/i18npro/i18npro.ts +++ b/src/i18npro/i18npro.ts @@ -1,31 +1,33 @@ -import { ChangeLanguage } from "../types/i18npro.types"; -import { DynamicData, I18Message, I18Dictionary } from "../types"; -import { http, objectKeysToLower } from "../utils"; +import { ChangeLanguage } from '../types/i18npro.types' +import { DynamicData, I18Message, I18Dictionary } from '../types' +import { http, objectKeysToLower } from '../utils' export class _i18nPro { - private storedLocales: string[] = []; - private isLoadingLanguage: boolean = false; - private messages: I18Dictionary = {}; - - public locale!: string; - public defaultLocale = ""; + private storedLocales: string[] = [] + private isLoadingLanguage: boolean = false + private messages: I18Dictionary = {} + + public locale!: string + public defaultLocale = '' constructor() {} private getPluralFromArgs(args: any[]): 1 | 2 | 3 | undefined { - return typeof args[0] === "number" && args[0] > 0 && args[0] < 4? (args[0] as 1 | 2 | 3) : undefined; + return typeof args[0] === 'number' && args[0] > 0 && args[0] < 4 + ? (args[0] as 1 | 2 | 3) + : undefined } private getDynamicDataFromArgs(args: any[]): DynamicData | undefined { - const resArg0 = typeof args[0] === "object" ? args[0] : undefined - const resArg1 = typeof args[1] === "object" ? args[1] : undefined + const resArg0 = typeof args[0] === 'object' ? args[0] : undefined + const resArg1 = typeof args[1] === 'object' ? args[1] : undefined return resArg0 || resArg1 } private setLocaleMessages(locale: string, messages: I18Message) { - this.messages = { ...this.messages, [locale]: objectKeysToLower(messages) }; - }; + this.messages = { ...this.messages, [locale]: objectKeysToLower(messages) } + } - private setIsLoadingLanguage(val: boolean){ + private setIsLoadingLanguage(val: boolean) { this.isLoadingLanguage = val } @@ -36,50 +38,56 @@ export class _i18nPro { * @returns {string} error message */ private checkMessageObjectFormat(message: I18Message): string { - const messageKeys = Object.keys(message); + const messageKeys = Object.keys(message) if (messageKeys.length === 0) { - return "No keys found. Please check the JSON and return at least 1 translation"; + return 'No keys found. Please check the JSON and return at least 1 translation' } //TODO: improve this validation by extending objectKeysToLower() to accept a callback (val: string | number) => string // where val is the value of each key in the dictionary. The function will contains the validation below - if (Array.isArray(message) || Object.entries(message).find(([_, val]) => typeof val !== 'string' && typeof val !== 'number')) { - return "Invalid format for dictionary. It must be like a Record"; + if ( + Array.isArray(message) || + Object.entries(message).find( + ([_, val]) => typeof val !== 'string' && typeof val !== 'number' + ) + ) { + return 'Invalid format for dictionary. It must be like a Record' } - return ""; + return '' } private getRemoteMessages(apiUrl: string): Promise { - this.setIsLoadingLanguage(true); + this.setIsLoadingLanguage(true) // load locale messages with dynamic import return http({ - method: "GET", - url: `${apiUrl}`, + method: 'GET', + url: `${apiUrl}` }) .then((response) => { - this.setIsLoadingLanguage(false); - return response; + this.setIsLoadingLanguage(false) + return response }) .catch((error) => { - this.setIsLoadingLanguage(false); - return Promise.reject(error); - }); + this.setIsLoadingLanguage(false) + return Promise.reject(error) + }) } - private checkParseDictionary(data: string | Record){ + private checkParseDictionary(data: string | Record) { try { - const dictionary = typeof data === "string" - ? (JSON.parse(data) as Record) - : data; + const dictionary = + typeof data === 'string' + ? (JSON.parse(data) as Record) + : data return Promise.resolve(dictionary) - } catch(err){ + } catch (err) { return Promise.reject(err) } } public setLocale(locale: string) { - this.storedLocales = [...new Set([...this.storedLocales, locale])]; - this.locale = locale; - }; + this.storedLocales = [...new Set([...this.storedLocales, locale])] + this.locale = locale + } public isLocaleAvailable(locale: string): boolean { return this.storedLocales.includes(locale) @@ -89,93 +97,101 @@ export class _i18nPro { locale: string, apiUrl: string ): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (this.isLoadingLanguage) { - return resolve(undefined); + return resolve(undefined) } if (this.locale === locale) { - resolve(this.locale); + resolve(this.locale) } - const alreadyLoaded = this.storedLocales.includes(locale); + const alreadyLoaded = this.storedLocales.includes(locale) if (alreadyLoaded) { - this.setLocale(locale); - return resolve(locale); + this.setLocale(locale) + return resolve(locale) } else { - return this.getRemoteMessages(apiUrl).then((messages) => { - const error = this.checkMessageObjectFormat(messages); - if (error) { - return reject(error); - } - this.setLocaleMessages(locale, messages); - this.setLocale(locale); - return resolve(locale); - }).catch(reject) + return this.getRemoteMessages(apiUrl) + .then((messages) => { + const error = this.checkMessageObjectFormat(messages) + if (error) { + return reject(error) + } + this.setLocaleMessages(locale, messages) + this.setLocale(locale) + return resolve(locale) + }) + .catch(reject) } - }); + }) } public loadLocalMessages( locale: string, messages: string | Record ): Promise { - return this.checkParseDictionary(messages).then(res => { + return this.checkParseDictionary(messages).then((res) => { let dictionary: Record = res - const error = this.checkMessageObjectFormat(dictionary); + const error = this.checkMessageObjectFormat(dictionary) if (error) { - return Promise.reject(error); + return Promise.reject(error) } - this.setLocale(locale); - this.setLocaleMessages(locale, dictionary); - return Promise.resolve(locale); + this.setLocale(locale) + this.setLocaleMessages(locale, dictionary) + return Promise.resolve(locale) }) } - public changeLanguage: ChangeLanguage = (...args): Promise => { - const newLocale = args[0] + public changeLanguage: ChangeLanguage = ( + ...args + ): Promise => { + const newLocale = args[0] if (!newLocale) { - const errMsg = "Provide a locale"; - console.error(errMsg); - return Promise.reject({message: errMsg, data: newLocale}) + const errMsg = 'Provide a locale' + console.error(errMsg) + return Promise.reject({ message: errMsg, data: newLocale }) } - return this.checkParseDictionary(args[1]).then(res => { - return this.loadLocalMessages(newLocale, res) - }).catch(() => { - const apiUrl = typeof args[1] === 'string'? args[1] : undefined - if(apiUrl){ - return this.loadMessages(newLocale, apiUrl) - } else { - return Promise.reject({message: 'Arguments provided are not valid', data: {newLocale, args: args[1]}}) - } - }) - + return this.checkParseDictionary(args[1]) + .then((res) => { + return this.loadLocalMessages(newLocale, res) + }) + .catch(() => { + const apiUrl = typeof args[1] === 'string' ? args[1] : undefined + if (apiUrl) { + return this.loadMessages(newLocale, apiUrl) + } else { + return Promise.reject({ + message: 'Arguments provided are not valid', + data: { newLocale, args: args[1] } + }) + } + }) } public t(value: string, ...args: any[]): string { - const flatArgs = args.flat(); - const plural = this.getPluralFromArgs(flatArgs); - const dynamicData = this.getDynamicDataFromArgs(flatArgs); + const flatArgs = args.flat() + const plural = this.getPluralFromArgs(flatArgs) + const dynamicData = this.getDynamicDataFromArgs(flatArgs) - const locale = this.locale || this.defaultLocale; + const locale = this.locale || this.defaultLocale const translationKey = this.messages[locale] && this.messages[locale]![value.toLocaleLowerCase()] ? value.toLocaleLowerCase() - : undefined; + : undefined let translation = translationKey ? `${this.messages[locale]![translationKey]}` - : value; + : value if (translationKey && plural) { - translation = translation.split("|")[plural - 1] || value; + translation = translation.split('|')[plural - 1] || value } if (dynamicData) { for (const key in dynamicData) { - translation = translation.replace(`{${key}}`, dynamicData[key] || key); + translation = translation.replace(`{${key}}`, dynamicData[key] || key) } } - return translation.trim(); + return translation.trim() } } -const i18nPro = new _i18nPro(); -export default i18nPro; +const i18nPro = new _i18nPro() +export default i18nPro diff --git a/src/i18npro/index.ts b/src/i18npro/index.ts index d7c52cc..f178b10 100644 --- a/src/i18npro/index.ts +++ b/src/i18npro/index.ts @@ -1 +1 @@ -export {default as i18nPro} from './i18npro' \ No newline at end of file +export { default as i18nPro } from './i18npro' diff --git a/src/index.ts b/src/index.ts index d7eb9c7..1978cdf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,7 @@ -export type { DynamicData, I18Message, I18Dictionary, ChangeLanguage } from "./types"; -export { i18nPro } from "./i18npro"; +export type { + DynamicData, + I18Message, + I18Dictionary, + ChangeLanguage +} from './types' +export { i18nPro } from './i18npro' diff --git a/src/test/i18npro/i18npro-translate.spec.ts b/src/test/i18npro/i18npro-translate.spec.ts index ae755ab..ef1b808 100644 --- a/src/test/i18npro/i18npro-translate.spec.ts +++ b/src/test/i18npro/i18npro-translate.spec.ts @@ -1,48 +1,47 @@ -import { i18nPro } from "../../i18npro" -import { I18Message } from "../../types" +import { i18nPro } from '../../i18npro' +import { I18Message } from '../../types' describe('Test translate method of i18nPro class', () => { + const dictionary: I18Message = { + simple: ' Simple ', + plural: 'One | Multi | Ultra', + plural2: 'One | Multi ', + date: 'Today is {date}' + } - const dictionary: I18Message = { - simple: " Simple ", - plural: "One | Multi | Ultra", - plural2: "One | Multi ", - date: "Today is {date}" - } + beforeAll(() => { + i18nPro.loadLocalMessages('en', dictionary) + }) - beforeAll(() => { - i18nPro.loadLocalMessages('en', dictionary) - }) - - it('t => return key value', () => { - expect(i18nPro.t('Not found')).toEqual('Not found') - }) - it('t => return key value and remove extra whitespaces', () => { - expect(i18nPro.t(' Not found ')).toEqual('Not found') - }) - it('t => return the value for matching key', () => { - expect(i18nPro.t('simple')).toEqual('Simple') - }) - it('t => return plural at index 0', () => { - expect(i18nPro.t('plural', 1)).toEqual('One') - }) - it('t => return plural at index 1', () => { - expect(i18nPro.t('plural', 2)).toEqual('Multi') - }) - it('t => return plural at index 2', () => { - expect(i18nPro.t('plural', 3)).toEqual('Ultra') - }) - it('t => plural at index 3 return default value', () => { - expect(i18nPro.t('plural2', 3)).toEqual('plural2') - }) - it('t => return translation with dynamic value', () => { - const date = Date.now().toLocaleString() - expect(i18nPro.t('date', {date})).toEqual(`Today is ${date}`) - }) - it('t => translation with dynamic value returns default value', () => { - expect(i18nPro.t('date', {})).toEqual(`Today is {date}`) - }) - it('t => translation with dynamic value returns default value', () => { - expect(i18nPro.t('date', {date: ""})).toEqual(`Today is date`) - }) -}) \ No newline at end of file + it('t => return key value', () => { + expect(i18nPro.t('Not found')).toEqual('Not found') + }) + it('t => return key value and remove extra whitespaces', () => { + expect(i18nPro.t(' Not found ')).toEqual('Not found') + }) + it('t => return the value for matching key', () => { + expect(i18nPro.t('simple')).toEqual('Simple') + }) + it('t => return plural at index 0', () => { + expect(i18nPro.t('plural', 1)).toEqual('One') + }) + it('t => return plural at index 1', () => { + expect(i18nPro.t('plural', 2)).toEqual('Multi') + }) + it('t => return plural at index 2', () => { + expect(i18nPro.t('plural', 3)).toEqual('Ultra') + }) + it('t => plural at index 3 return default value', () => { + expect(i18nPro.t('plural2', 3)).toEqual('plural2') + }) + it('t => return translation with dynamic value', () => { + const date = Date.now().toLocaleString() + expect(i18nPro.t('date', { date })).toEqual(`Today is ${date}`) + }) + it('t => translation with dynamic value returns default value', () => { + expect(i18nPro.t('date', {})).toEqual(`Today is {date}`) + }) + it('t => translation with dynamic value returns default value', () => { + expect(i18nPro.t('date', { date: '' })).toEqual(`Today is date`) + }) +}) diff --git a/src/test/i18npro/i18npro.spec.ts b/src/test/i18npro/i18npro.spec.ts index 8dc6f72..c1cef40 100644 --- a/src/test/i18npro/i18npro.spec.ts +++ b/src/test/i18npro/i18npro.spec.ts @@ -1,396 +1,496 @@ - -import { _i18nPro } from "../../i18npro/i18npro"; -import { i18nPro } from "../../i18npro" +import { _i18nPro } from '../../i18npro/i18npro' +import { i18nPro } from '../../i18npro' describe('Test i18nPro class with all its methods', () => { + const successfulResponseDictionary = Promise.resolve({ + json: () => Promise.resolve({ success: 'true' }), + ok: true, + status: 200, + headers: {} as Headers + } as Response) + const invalidDictionaryResponseEmpty = Promise.resolve({ + json: () => Promise.resolve({}), + ok: true, + status: 200, + headers: {} as Headers + } as Response) + const invalidDictionaryResponseFormat = Promise.resolve({ + json: () => Promise.resolve({ abc: true }), + ok: true, + status: 200, + headers: {} as Headers + } as Response) + const errorResponseDictionary = Promise.resolve({ + json: () => Promise.reject(), + ok: false, + status: 500, + headers: {} as Headers + } as Response) - const successfulResponseDictionary = Promise.resolve({ - json: () => Promise.resolve({ "success": "true" }), - ok: true, - status: 200, - headers: {} as Headers - } as Response); - const invalidDictionaryResponseEmpty = Promise.resolve({ - json: () => Promise.resolve({}), - ok: true, - status: 200, - headers: {} as Headers - } as Response); - const invalidDictionaryResponseFormat = Promise.resolve({ - json: () => Promise.resolve({abc: true}), - ok: true, - status: 200, - headers: {} as Headers - } as Response); - const errorResponseDictionary = Promise.resolve({ - json: () => Promise.reject(), - ok: false, - status: 500, - headers: {} as Headers - } as Response); - - it('Default language', () => { - i18nPro.defaultLocale = "en-US"; - expect(i18nPro.defaultLocale).toEqual('en-US') - }) - it('getPluralFromArgs => undefined', () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getPluralFromArgs') - i18nPro.t('test') - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', {obj: 12}) - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', "dfgdf") - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', 0) - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', 4) - expect(methodSpy).toReturnWith(undefined) - }) - it('getPluralFromArgs => number', () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getPluralFromArgs') - i18nPro.t('test', 1) - expect(methodSpy).toReturnWith(1) - methodSpy.mockClear() - i18nPro.t('test', 2) - expect(methodSpy).toReturnWith(2) - methodSpy.mockClear() - i18nPro.t('test', 3) - expect(methodSpy).toReturnWith(3) + it('Default language', () => { + i18nPro.defaultLocale = 'en-US' + expect(i18nPro.defaultLocale).toEqual('en-US') + }) + it('getPluralFromArgs => undefined', () => { + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getPluralFromArgs') + i18nPro.t('test') + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', { obj: 12 }) + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 'dfgdf') + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 0) + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 4) + expect(methodSpy).toReturnWith(undefined) + }) + it('getPluralFromArgs => number', () => { + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getPluralFromArgs') + i18nPro.t('test', 1) + expect(methodSpy).toReturnWith(1) + methodSpy.mockClear() + i18nPro.t('test', 2) + expect(methodSpy).toReturnWith(2) + methodSpy.mockClear() + i18nPro.t('test', 3) + expect(methodSpy).toReturnWith(3) + }) + it('getDynamicDataFromArgs => undefined', () => { + const methodSpy = jest.spyOn( + _i18nPro.prototype as any, + 'getDynamicDataFromArgs' + ) + i18nPro.t('test', 1, 3564) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith([1, 3564]) + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 1, 'sgewsg') + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 'dfbsadf', ['string']) + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + i18nPro.t('test', 'dafg', () => {}) + expect(methodSpy).toReturnWith(undefined) + methodSpy.mockClear() + }) + it('getDynamicDataFromArgs => object', () => { + const methodSpy = jest.spyOn( + _i18nPro.prototype as any, + 'getDynamicDataFromArgs' + ) + i18nPro.t('test', { var: 12 }) + expect(methodSpy).toReturnWith({ var: 12 }) + methodSpy.mockClear() + i18nPro.t('test', 1, { 23: 'sdfgjk' }) + expect(methodSpy).toReturnWith({ 23: 'sdfgjk' }) + methodSpy.mockClear() + }) + it('setLocale => called from LoadMessages', async () => { + global.fetch = jest.fn(() => successfulResponseDictionary) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocale') + expect(i18nPro['locale']).toBe(undefined) + expect(i18nPro['storedLocales']).toEqual([]) + await i18nPro.loadMessages('en', '/') + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('en') + expect(i18nPro['locale']).toBe('en') + expect(i18nPro['storedLocales']).toEqual(['en']) + methodSpy.mockClear() + }) + it('setLocale => called from LoadLocalMessages', async () => { + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocale') + expect(i18nPro['locale']).toBe('en') + expect(i18nPro['storedLocales']).toEqual(['en']) + await i18nPro.loadLocalMessages('en', { test: 12 }) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('en') + expect(i18nPro['locale']).toBe('en') + expect(i18nPro['storedLocales']).toEqual(['en']) + methodSpy.mockClear() + await i18nPro.loadLocalMessages('en-us', { test: 12 }) + expect(methodSpy).toHaveBeenCalledWith('en-us') + expect(i18nPro['locale']).toBe('en-us') + expect(i18nPro['storedLocales']).toEqual(['en', 'en-us']) + methodSpy.mockClear() + }) + it('setLocaleMessages', async () => { + const newI18pro = new _i18nPro() + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocaleMessages') + const dictionary = { test: 12, a: 'sfdhnjgb' } + expect(newI18pro['messages']).toEqual({}) + newI18pro['setLocaleMessages']('en', dictionary) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('en', dictionary) + expect(newI18pro['messages']).toEqual({ en: dictionary }) + methodSpy.mockClear() + newI18pro['setLocaleMessages']('en', dictionary) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('en', dictionary) + expect(newI18pro['messages']).toEqual({ en: dictionary }) + methodSpy.mockClear() + newI18pro['setLocaleMessages']('en-us', { ...dictionary, en: 'true' }) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('en-us', { + ...dictionary, + en: 'true' }) - it('getDynamicDataFromArgs => undefined', () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getDynamicDataFromArgs') - i18nPro.t('test', 1, 3564) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith([1, 3564]) - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', 1, "sgewsg") - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', "dfbsadf", ['string']) - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - i18nPro.t('test', "dafg", () => {}) - expect(methodSpy).toReturnWith(undefined) - methodSpy.mockClear() - }) - it('getDynamicDataFromArgs => object', () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getDynamicDataFromArgs') - i18nPro.t('test', {var: 12}) - expect(methodSpy).toReturnWith({var: 12}) - methodSpy.mockClear() - i18nPro.t('test', 1, {23: "sdfgjk"}) - expect(methodSpy).toReturnWith({23: "sdfgjk"}) - methodSpy.mockClear() + expect(newI18pro['messages']).toEqual({ + en: dictionary, + 'en-us': { ...dictionary, en: 'true' } }) - it('setLocale => called from LoadMessages', async () => { - global.fetch = jest.fn(() => successfulResponseDictionary) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocale') - expect(i18nPro['locale']).toBe(undefined) - expect(i18nPro['storedLocales']).toEqual([]) - await i18nPro.loadMessages('en', '/') - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('en') - expect(i18nPro['locale']).toBe('en') - expect(i18nPro['storedLocales']).toEqual(['en']) - methodSpy.mockClear() - }) - it('setLocale => called from LoadLocalMessages', async () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocale') - expect(i18nPro['locale']).toBe('en') - expect(i18nPro['storedLocales']).toEqual(['en']) - await i18nPro.loadLocalMessages('en', {test: 12}) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('en') - expect(i18nPro['locale']).toBe('en') - expect(i18nPro['storedLocales']).toEqual(['en']) - methodSpy.mockClear() - await i18nPro.loadLocalMessages('en-us', {test: 12}) - expect(methodSpy).toHaveBeenCalledWith('en-us') - expect(i18nPro['locale']).toBe('en-us') - expect(i18nPro['storedLocales']).toEqual(['en', 'en-us']) - methodSpy.mockClear() - }) - it('setLocaleMessages', async () => { - const newI18pro = new _i18nPro() - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'setLocaleMessages') - const dictionary = {test: 12, a: 'sfdhnjgb'} - expect(newI18pro['messages']).toEqual({}) - newI18pro['setLocaleMessages']('en', dictionary) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('en', dictionary) - expect(newI18pro['messages']).toEqual({en: dictionary}) - methodSpy.mockClear() - newI18pro['setLocaleMessages']('en', dictionary) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('en', dictionary) - expect(newI18pro['messages']).toEqual({en: dictionary}) - methodSpy.mockClear() - newI18pro['setLocaleMessages']('en-us', {...dictionary, en: 'true'}) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('en-us', {...dictionary, en: 'true'}) - expect(newI18pro['messages']).toEqual({en: dictionary, 'en-us': {...dictionary, en: 'true'}}) - methodSpy.mockClear() - }) - it('checkMessageObjectFormat => no error', async () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'checkMessageObjectFormat') - const dictionary = {test: 12, a: 'sfdhnjgb'} - i18nPro['checkMessageObjectFormat'](dictionary) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith(dictionary) - expect(methodSpy).toReturnWith('') - methodSpy.mockClear() - }) - it('checkMessageObjectFormat => empty dictionary error', async () => { - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'checkMessageObjectFormat') - const dictionary = {} - i18nPro['checkMessageObjectFormat'](dictionary) - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith(dictionary) - expect(methodSpy).toReturnWith('No keys found. Please check the JSON and return at least 1 translation') - methodSpy.mockClear() - }) - it('checkMessageObjectFormat => invalid format', async () => { - const errMsg = 'Invalid format for dictionary. It must be like a Record' - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'checkMessageObjectFormat') - i18nPro['checkMessageObjectFormat'](["sdgrf"] as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - //pass function as value - i18nPro['checkMessageObjectFormat']({key: () => {}} as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - //pass object as value - i18nPro['checkMessageObjectFormat']({key: {val1: 12}} as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - //pass null as value - i18nPro['checkMessageObjectFormat']({key: null} as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - //pass undefined as value - i18nPro['checkMessageObjectFormat']({key: undefined} as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - //pass boolean as value - i18nPro['checkMessageObjectFormat']({key: true} as any) - expect(methodSpy).toReturnWith(errMsg) - methodSpy.mockClear() - }) - it('getRemoteMessages & Loading language propery => succeed', async () => { - global.fetch = jest.fn(() => successfulResponseDictionary) - const loading = jest.spyOn(_i18nPro.prototype as any, 'setIsLoadingLanguage') - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getRemoteMessages') - expect(i18nPro['isLoadingLanguage']).toBeFalsy() - const data = await i18nPro['getRemoteMessages']('/') - expect(i18nPro['isLoadingLanguage']).toBeFalsy() + methodSpy.mockClear() + }) + it('checkMessageObjectFormat => no error', async () => { + const methodSpy = jest.spyOn( + _i18nPro.prototype as any, + 'checkMessageObjectFormat' + ) + const dictionary = { test: 12, a: 'sfdhnjgb' } + i18nPro['checkMessageObjectFormat'](dictionary) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith(dictionary) + expect(methodSpy).toReturnWith('') + methodSpy.mockClear() + }) + it('checkMessageObjectFormat => empty dictionary error', async () => { + const methodSpy = jest.spyOn( + _i18nPro.prototype as any, + 'checkMessageObjectFormat' + ) + const dictionary = {} + i18nPro['checkMessageObjectFormat'](dictionary) + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith(dictionary) + expect(methodSpy).toReturnWith( + 'No keys found. Please check the JSON and return at least 1 translation' + ) + methodSpy.mockClear() + }) + it('checkMessageObjectFormat => invalid format', async () => { + const errMsg = + 'Invalid format for dictionary. It must be like a Record' + const methodSpy = jest.spyOn( + _i18nPro.prototype as any, + 'checkMessageObjectFormat' + ) + i18nPro['checkMessageObjectFormat'](['sdgrf'] as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + //pass function as value + i18nPro['checkMessageObjectFormat']({ key: () => {} } as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + //pass object as value + i18nPro['checkMessageObjectFormat']({ key: { val1: 12 } } as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + //pass null as value + i18nPro['checkMessageObjectFormat']({ key: null } as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + //pass undefined as value + i18nPro['checkMessageObjectFormat']({ key: undefined } as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + //pass boolean as value + i18nPro['checkMessageObjectFormat']({ key: true } as any) + expect(methodSpy).toReturnWith(errMsg) + methodSpy.mockClear() + }) + it('getRemoteMessages & Loading language propery => succeed', async () => { + global.fetch = jest.fn(() => successfulResponseDictionary) + const loading = jest.spyOn( + _i18nPro.prototype as any, + 'setIsLoadingLanguage' + ) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getRemoteMessages') + expect(i18nPro['isLoadingLanguage']).toBeFalsy() + const data = await i18nPro['getRemoteMessages']('/') + expect(i18nPro['isLoadingLanguage']).toBeFalsy() + expect(methodSpy).toHaveBeenCalled() + expect(methodSpy).toHaveBeenCalledWith('/') + expect(data).toEqual({ success: 'true' }) + expect(loading).toHaveBeenCalledTimes(2) + expect(loading).toHaveBeenNthCalledWith(1, true) + expect(loading).toHaveBeenNthCalledWith(2, false) + jest.clearAllMocks() + }) + it('getRemoteMessages & Loading language propery => error', async () => { + global.fetch = jest.fn(() => errorResponseDictionary) + const loading = jest.spyOn( + _i18nPro.prototype as any, + 'setIsLoadingLanguage' + ) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getRemoteMessages') + expect(i18nPro['isLoadingLanguage']).toBeFalsy() + expect(i18nPro['getRemoteMessages']('/')) + .rejects.toBeTruthy() + .finally(() => { expect(methodSpy).toHaveBeenCalled() expect(methodSpy).toHaveBeenCalledWith('/') - expect(data).toEqual({ "success": "true" }) + expect(i18nPro['isLoadingLanguage']).toBeFalsy() expect(loading).toHaveBeenCalledTimes(2) expect(loading).toHaveBeenNthCalledWith(1, true) expect(loading).toHaveBeenNthCalledWith(2, false) jest.clearAllMocks() - }) - it('getRemoteMessages & Loading language propery => error', async () => { - global.fetch = jest.fn(() => errorResponseDictionary) - const loading = jest.spyOn(_i18nPro.prototype as any, 'setIsLoadingLanguage') - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'getRemoteMessages') - expect(i18nPro['isLoadingLanguage']).toBeFalsy() - expect(i18nPro['getRemoteMessages']('/')).rejects.toBeTruthy().finally(() => { - expect(methodSpy).toHaveBeenCalled() - expect(methodSpy).toHaveBeenCalledWith('/') - expect(i18nPro['isLoadingLanguage']).toBeFalsy() - expect(loading).toHaveBeenCalledTimes(2) - expect(loading).toHaveBeenNthCalledWith(1, true) - expect(loading).toHaveBeenNthCalledWith(2, false) - jest.clearAllMocks() - }) - }) - it('loadMessages => succeed', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => successfulResponseDictionary) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - expect(freshI18nPro.loadMessages('en', '/api')).resolves.toEqual('en').then(() => { - expect(methodSpy).toHaveBeenCalledWith('en', '/api') - expect(freshI18nPro['locale']).toEqual('en') - expect(freshI18nPro['messages']).toEqual({'en': {success: 'true'}}) - expect(freshI18nPro['isLoadingLanguage']).toEqual(false) - expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalled() - expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledWith('/api') - jest.clearAllMocks() - }) - }) - it('loadMessages => error: rest api fails', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => errorResponseDictionary) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - expect(freshI18nPro.loadMessages('en', '/api')).rejects.toBeTruthy().finally(() => { - expect(methodSpy).toHaveBeenCalledWith('en', '/api') - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - expect(freshI18nPro['isLoadingLanguage']).toEqual(false) - jest.clearAllMocks() - }) - }) - it('loadMessages => error: invalid dictionary (empty)', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => invalidDictionaryResponseEmpty) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - expect(freshI18nPro.loadMessages('en', '/api')).rejects.toEqual('No keys found. Please check the JSON and return at least 1 translation').finally(() => { - expect(methodSpy).toHaveBeenCalledWith('en', '/api') - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - expect(freshI18nPro['isLoadingLanguage']).toEqual(false) - jest.clearAllMocks() - }) - }) - it('loadMessages => error: invalid dictionary (format)', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => invalidDictionaryResponseFormat) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - expect(freshI18nPro.loadMessages('en', '/api')).rejects.toEqual('Invalid format for dictionary. It must be like a Record').finally(() => { - expect(methodSpy).toHaveBeenCalledWith('en', '/api') - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - expect(freshI18nPro['isLoadingLanguage']).toEqual(false) - jest.clearAllMocks() - }) - }) - it('loadMessages => succeed: same active locale', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => successfulResponseDictionary) - const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - await freshI18nPro.loadMessages('en', '/api') - expect(freshI18nPro.loadMessages('en', '/api')).resolves.toEqual('en').finally(() => { - expect(methodSpy).toHaveBeenCalledWith('en', '/api') - expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledTimes(1) - expect(freshI18nPro['locale']).toEqual('en') - expect(freshI18nPro['messages']).toEqual({'en': {success: 'true'}}) - expect(freshI18nPro['isLoadingLanguage']).toEqual(false) - jest.clearAllMocks() - }) - }) - it('loadMessages => succeed: already loaded locale', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => successfulResponseDictionary) - expect(Promise.all([ - freshI18nPro.loadMessages('en', '/api'), - freshI18nPro.loadMessages('en', '/api') - ])).resolves.toContain(undefined).finally(() => { - expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledTimes(1) - expect(freshI18nPro['messages']).toEqual({'en': {success: 'true'}}) - jest.clearAllMocks() - }) - }) - it('loadLocalMessages => succeed: dictionary as object', async () => { - const freshI18nPro = new _i18nPro() - expect(freshI18nPro.loadLocalMessages('en', {success: "true"})).resolves.toEqual('en').finally(() => { - expect(freshI18nPro['messages']).toEqual({'en': {success: 'true'}}) - expect(freshI18nPro['locale']).toEqual('en') - jest.clearAllMocks() - }) - }) - it('loadLocalMessages => succeed: dictionary as string', async () => { - const freshI18nPro = new _i18nPro() - expect(freshI18nPro.loadLocalMessages('en', '{"success": "true"}')).resolves.toEqual('en').finally(() => { - expect(freshI18nPro['locale']).toEqual('en') - expect(freshI18nPro['messages']).toEqual({'en': {success: 'true'}}) - jest.clearAllMocks() - }) - }) - it('loadLocalMessages => error: invalid json as string', async () => { - const freshI18nPro = new _i18nPro() - expect(freshI18nPro.loadLocalMessages('en', '{success: "true"}')).rejects.toBeTruthy().finally(() => { - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - jest.clearAllMocks() - }) - }) - it('loadLocalMessages => error: invalid dictionary object', async () => { - const freshI18nPro = new _i18nPro() - expect(freshI18nPro.loadLocalMessages('en', '{"success": true}')).rejects.toEqual('Invalid format for dictionary. It must be like a Record').finally(() => { - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - jest.clearAllMocks() - }) - }) - it('changeLanguage => succeed: load from api', async () => { - const freshI18nPro = new _i18nPro() - global.fetch = jest.fn(() => successfulResponseDictionary) - const loadLocalMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadLocalMessages') - expect(freshI18nPro.changeLanguage('en-us', '/api')).resolves.toEqual('en-us').then(() => { - expect(freshI18nPro.loadMessages).toHaveBeenCalled() - expect(loadLocalMessagesMock).not.toHaveBeenCalled() - expect(freshI18nPro['locale']).toEqual('en-us') - expect(freshI18nPro['messages']).toEqual({'en-us': {success: 'true'}}) - jest.clearAllMocks() - }) - }) - it('changeLanguage => succeed: load from local', async () => { - const freshI18nPro = new _i18nPro() - const loadMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - expect(freshI18nPro.changeLanguage('en-us', '{"dictionary": "my translation"}')).resolves.toEqual('en-us').then(() => { - expect(freshI18nPro.loadLocalMessages).toHaveBeenCalled() - expect(loadMessagesMock).not.toHaveBeenCalled() - expect(freshI18nPro['locale']).toEqual('en-us') - expect(freshI18nPro['messages']).toEqual({'en-us': {"dictionary": "my translation"}}) - jest.clearAllMocks() - }) - }) - it('changeLanguage => error: load from api with empty url', async () => { - const freshI18nPro = new _i18nPro() - const loadMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - const loadLocalMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadLocalMessages') - expect(freshI18nPro.changeLanguage('en-us', '')).rejects.toEqual({message: 'Arguments provided are not valid', data: {newLocale: 'en-us', args: ''}}).finally(() => { - expect(loadLocalMessagesMock).not.toHaveBeenCalled() - expect(loadMessagesMock).not.toHaveBeenCalled() - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - jest.clearAllMocks() - }) - }) - it('changeLanguage => error: load from api with empty url', async () => { - const freshI18nPro = new _i18nPro() - expect(freshI18nPro.changeLanguage('en-us', false as any)).rejects.toEqual({message: 'Arguments provided are not valid', data: {newLocale: 'en-us', args: false}}).finally(() => { - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - jest.clearAllMocks() + }) + }) + it('loadMessages => succeed', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => successfulResponseDictionary) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') + expect(freshI18nPro.loadMessages('en', '/api')) + .resolves.toEqual('en') + .then(() => { + expect(methodSpy).toHaveBeenCalledWith('en', '/api') + expect(freshI18nPro['locale']).toEqual('en') + expect(freshI18nPro['messages']).toEqual({ en: { success: 'true' } }) + expect(freshI18nPro['isLoadingLanguage']).toEqual(false) + expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalled() + expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledWith('/api') + jest.clearAllMocks() + }) + }) + it('loadMessages => error: rest api fails', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => errorResponseDictionary) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') + expect(freshI18nPro.loadMessages('en', '/api')) + .rejects.toBeTruthy() + .finally(() => { + expect(methodSpy).toHaveBeenCalledWith('en', '/api') + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + expect(freshI18nPro['isLoadingLanguage']).toEqual(false) + jest.clearAllMocks() + }) + }) + it('loadMessages => error: invalid dictionary (empty)', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => invalidDictionaryResponseEmpty) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') + expect(freshI18nPro.loadMessages('en', '/api')) + .rejects.toEqual( + 'No keys found. Please check the JSON and return at least 1 translation' + ) + .finally(() => { + expect(methodSpy).toHaveBeenCalledWith('en', '/api') + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + expect(freshI18nPro['isLoadingLanguage']).toEqual(false) + jest.clearAllMocks() + }) + }) + it('loadMessages => error: invalid dictionary (format)', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => invalidDictionaryResponseFormat) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') + expect(freshI18nPro.loadMessages('en', '/api')) + .rejects.toEqual( + 'Invalid format for dictionary. It must be like a Record' + ) + .finally(() => { + expect(methodSpy).toHaveBeenCalledWith('en', '/api') + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + expect(freshI18nPro['isLoadingLanguage']).toEqual(false) + jest.clearAllMocks() + }) + }) + it('loadMessages => succeed: same active locale', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => successfulResponseDictionary) + const methodSpy = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') + await freshI18nPro.loadMessages('en', '/api') + expect(freshI18nPro.loadMessages('en', '/api')) + .resolves.toEqual('en') + .finally(() => { + expect(methodSpy).toHaveBeenCalledWith('en', '/api') + expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledTimes(1) + expect(freshI18nPro['locale']).toEqual('en') + expect(freshI18nPro['messages']).toEqual({ en: { success: 'true' } }) + expect(freshI18nPro['isLoadingLanguage']).toEqual(false) + jest.clearAllMocks() + }) + }) + it('loadMessages => succeed: already loaded locale', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => successfulResponseDictionary) + expect( + Promise.all([ + freshI18nPro.loadMessages('en', '/api'), + freshI18nPro.loadMessages('en', '/api') + ]) + ) + .resolves.toContain(undefined) + .finally(() => { + expect(freshI18nPro['getRemoteMessages']).toHaveBeenCalledTimes(1) + expect(freshI18nPro['messages']).toEqual({ en: { success: 'true' } }) + jest.clearAllMocks() + }) + }) + it('loadLocalMessages => succeed: dictionary as object', async () => { + const freshI18nPro = new _i18nPro() + expect(freshI18nPro.loadLocalMessages('en', { success: 'true' })) + .resolves.toEqual('en') + .finally(() => { + expect(freshI18nPro['messages']).toEqual({ en: { success: 'true' } }) + expect(freshI18nPro['locale']).toEqual('en') + jest.clearAllMocks() + }) + }) + it('loadLocalMessages => succeed: dictionary as string', async () => { + const freshI18nPro = new _i18nPro() + expect(freshI18nPro.loadLocalMessages('en', '{"success": "true"}')) + .resolves.toEqual('en') + .finally(() => { + expect(freshI18nPro['locale']).toEqual('en') + expect(freshI18nPro['messages']).toEqual({ en: { success: 'true' } }) + jest.clearAllMocks() + }) + }) + it('loadLocalMessages => error: invalid json as string', async () => { + const freshI18nPro = new _i18nPro() + expect(freshI18nPro.loadLocalMessages('en', '{success: "true"}')) + .rejects.toBeTruthy() + .finally(() => { + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + jest.clearAllMocks() + }) + }) + it('loadLocalMessages => error: invalid dictionary object', async () => { + const freshI18nPro = new _i18nPro() + expect(freshI18nPro.loadLocalMessages('en', '{"success": true}')) + .rejects.toEqual( + 'Invalid format for dictionary. It must be like a Record' + ) + .finally(() => { + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + jest.clearAllMocks() + }) + }) + it('changeLanguage => succeed: load from api', async () => { + const freshI18nPro = new _i18nPro() + global.fetch = jest.fn(() => successfulResponseDictionary) + const loadLocalMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadLocalMessages' + ) + expect(freshI18nPro.changeLanguage('en-us', '/api')) + .resolves.toEqual('en-us') + .then(() => { + expect(freshI18nPro.loadMessages).toHaveBeenCalled() + expect(loadLocalMessagesMock).not.toHaveBeenCalled() + expect(freshI18nPro['locale']).toEqual('en-us') + expect(freshI18nPro['messages']).toEqual({ + 'en-us': { success: 'true' } }) - }) - it('changeLanguage => error: locale not provided', async () => { - const freshI18nPro = new _i18nPro() - const loadMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadMessages') - const loadLocalMessagesMock = jest.spyOn(_i18nPro.prototype as any, 'loadLocalMessages') - expect(freshI18nPro.changeLanguage('', '')).rejects.toEqual({message: 'Provide a locale', data: ''}).finally(() => { - expect(loadLocalMessagesMock).not.toHaveBeenCalled() - expect(loadMessagesMock).not.toHaveBeenCalled() - expect(freshI18nPro['locale']).toEqual(undefined) - expect(freshI18nPro['messages']).toEqual({}) - jest.clearAllMocks() + jest.clearAllMocks() + }) + }) + it('changeLanguage => succeed: load from local', async () => { + const freshI18nPro = new _i18nPro() + const loadMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadMessages' + ) + expect( + freshI18nPro.changeLanguage('en-us', '{"dictionary": "my translation"}') + ) + .resolves.toEqual('en-us') + .then(() => { + expect(freshI18nPro.loadLocalMessages).toHaveBeenCalled() + expect(loadMessagesMock).not.toHaveBeenCalled() + expect(freshI18nPro['locale']).toEqual('en-us') + expect(freshI18nPro['messages']).toEqual({ + 'en-us': { dictionary: 'my translation' } }) - }) - it('changeLanguage => duplicate call returns locale', async () => { - const freshI18nPro = new _i18nPro() - freshI18nPro['setLocale']('es') - expect(Promise.all([ - freshI18nPro.changeLanguage('en-us', '/api'), - freshI18nPro.changeLanguage('en', '/api') - ])).resolves.toContain(undefined) - }) - it('isLocaleAvailable => should return true', async () => { - const freshI18nPro = new _i18nPro() - freshI18nPro['setLocale']('es') - expect(freshI18nPro.isLocaleAvailable('es')).toStrictEqual(true) - }) - it('isLocaleAvailable => should return false', async () => { - const freshI18nPro = new _i18nPro() - freshI18nPro['setLocale']('es') - expect(freshI18nPro.isLocaleAvailable('en')).toStrictEqual(false) - }) -}) \ No newline at end of file + jest.clearAllMocks() + }) + }) + it('changeLanguage => error: load from api with empty url', async () => { + const freshI18nPro = new _i18nPro() + const loadMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadMessages' + ) + const loadLocalMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadLocalMessages' + ) + expect(freshI18nPro.changeLanguage('en-us', '')) + .rejects.toEqual({ + message: 'Arguments provided are not valid', + data: { newLocale: 'en-us', args: '' } + }) + .finally(() => { + expect(loadLocalMessagesMock).not.toHaveBeenCalled() + expect(loadMessagesMock).not.toHaveBeenCalled() + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + jest.clearAllMocks() + }) + }) + it('changeLanguage => error: load from api with empty url', async () => { + const freshI18nPro = new _i18nPro() + expect(freshI18nPro.changeLanguage('en-us', false as any)) + .rejects.toEqual({ + message: 'Arguments provided are not valid', + data: { newLocale: 'en-us', args: false } + }) + .finally(() => { + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + jest.clearAllMocks() + }) + }) + it('changeLanguage => error: locale not provided', async () => { + const freshI18nPro = new _i18nPro() + const loadMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadMessages' + ) + const loadLocalMessagesMock = jest.spyOn( + _i18nPro.prototype as any, + 'loadLocalMessages' + ) + expect(freshI18nPro.changeLanguage('', '')) + .rejects.toEqual({ message: 'Provide a locale', data: '' }) + .finally(() => { + expect(loadLocalMessagesMock).not.toHaveBeenCalled() + expect(loadMessagesMock).not.toHaveBeenCalled() + expect(freshI18nPro['locale']).toEqual(undefined) + expect(freshI18nPro['messages']).toEqual({}) + jest.clearAllMocks() + }) + }) + it('changeLanguage => duplicate call returns locale', async () => { + const freshI18nPro = new _i18nPro() + freshI18nPro['setLocale']('es') + expect( + Promise.all([ + freshI18nPro.changeLanguage('en-us', '/api'), + freshI18nPro.changeLanguage('en', '/api') + ]) + ).resolves.toContain(undefined) + }) + it('isLocaleAvailable => should return true', async () => { + const freshI18nPro = new _i18nPro() + freshI18nPro['setLocale']('es') + expect(freshI18nPro.isLocaleAvailable('es')).toStrictEqual(true) + }) + it('isLocaleAvailable => should return false', async () => { + const freshI18nPro = new _i18nPro() + freshI18nPro['setLocale']('es') + expect(freshI18nPro.isLocaleAvailable('en')).toStrictEqual(false) + }) +}) diff --git a/src/test/i18npro/i18nproPerformance.spec.ts b/src/test/i18npro/i18nproPerformance.spec.ts index 45f7e51..b9c2556 100644 --- a/src/test/i18npro/i18nproPerformance.spec.ts +++ b/src/test/i18npro/i18nproPerformance.spec.ts @@ -1,51 +1,51 @@ -import { i18nPro } from "../../i18npro"; +import { i18nPro } from '../../i18npro' import os from 'os' const getRandomInt = (min: number, max: number) => { - return Math.floor(Math.random() * (max - min + 1) + min); + return Math.floor(Math.random() * (max - min + 1) + min) } function measureMemoryUsage() { - const used = process.memoryUsage(); - - console.log(`Memory Usage:`); - console.log(` - RSS: ${Math.round(used.rss / (1024 * 1024))} MB`); - console.log(` - Heap Total: ${Math.round(used.heapTotal / (1024 * 1024))} MB`); - console.log(` - Heap Used: ${Math.round(used.heapUsed / (1024 * 1024))} MB`); -} - + const used = process.memoryUsage() + console.log(`Memory Usage:`) + console.log(` - RSS: ${Math.round(used.rss / (1024 * 1024))} MB`) + console.log( + ` - Heap Total: ${Math.round(used.heapTotal / (1024 * 1024))} MB` + ) + console.log(` - Heap Used: ${Math.round(used.heapUsed / (1024 * 1024))} MB`) +} -describe("Test i18nPro performance", () => { - const jsonObject: Record = {}; - const totalTransaltions = 150000; +describe('Test i18nPro performance', () => { + const jsonObject: Record = {} + const totalTransaltions = 150000 for (let i = 1; i <= totalTransaltions; i++) { - const random = Math.floor(Math.random() * 100); - const val = random > 50? i : `value${i}` - jsonObject[`key${i}`] = val; + const random = Math.floor(Math.random() * 100) + const val = random > 50 ? i : `value${i}` + jsonObject[`key${i}`] = val } - const jsonString = JSON.stringify(jsonObject); + const jsonString = JSON.stringify(jsonObject) it('Translation method', async () => { - console.time(`processed ${totalTransaltions}`); - await i18nPro.loadLocalMessages("it-IT", jsonString); - console.timeEnd(`processed ${totalTransaltions}`); - await i18nPro.loadLocalMessages("en-US", jsonString); - await i18nPro.loadLocalMessages("en-UK", jsonString); - const translationCount = 500; - const timeStart = Date.now(); + console.time(`processed ${totalTransaltions}`) + await i18nPro.loadLocalMessages('it-IT', jsonString) + console.timeEnd(`processed ${totalTransaltions}`) + await i18nPro.loadLocalMessages('en-US', jsonString) + await i18nPro.loadLocalMessages('en-UK', jsonString) + const translationCount = 500 + const timeStart = Date.now() for (let i = 0; i <= translationCount; i++) { //console.time("processed key2000000"); - i18nPro.t(`key${getRandomInt(1, totalTransaltions)}`); + i18nPro.t(`key${getRandomInt(1, totalTransaltions)}`) //console.timeEnd("processed key2000000"); } - const delta = Date.now() - timeStart; + const delta = Date.now() - timeStart //console.log(`${delta} ms`); console.table( [{ translations: translationCount, time: `${delta} ms` }], - ["translations", "time"] - ); + ['translations', 'time'] + ) measureMemoryUsage() - expect(delta).toBeLessThanOrEqual(80); + expect(delta).toBeLessThanOrEqual(80) }) -}); +}) diff --git a/src/test/utils/http.spec.ts b/src/test/utils/http.spec.ts index bef3467..bb24e56 100644 --- a/src/test/utils/http.spec.ts +++ b/src/test/utils/http.spec.ts @@ -1,92 +1,91 @@ -import { HttpOptions } from "../../types"; -import { http } from "../../utils"; +import { HttpOptions } from '../../types' +import { http } from '../../utils' -describe("Test http method", () => { +describe('Test http method', () => { const successfulResponse = Promise.resolve({ json: () => Promise.resolve({ succes: true }), ok: true, status: 200, headers: {} as Headers - } as Response); - + } as Response) + const responseKo = Promise.resolve({ json: () => Promise.resolve({ succes: true }), ok: false, status: 500, headers: {} as Headers - } as Response); + } as Response) const responseError = Promise.reject({ status: 500, message: 'error' - }); + }) const httpArguments: HttpOptions = { - method: "POST", - url: "/test", + method: 'POST', + url: '/test', data: { data: 123 }, headers: { - Authorization: "Bearer token" + Authorization: 'Bearer token' } - }; + } - it("Check all given properties match", async () => { - global.fetch = jest.fn(() => successfulResponse); + it('Check all given properties match', async () => { + global.fetch = jest.fn(() => successfulResponse) - await http<{ succes: true }>({...httpArguments, data: undefined}); + await http<{ succes: true }>({ ...httpArguments, data: undefined }) - expect(fetch).toHaveBeenCalledWith("/test", { - method: "POST", + expect(fetch).toHaveBeenCalledWith('/test', { + method: 'POST', body: undefined, headers: { - "Content-Type": "application/json", - Authorization: "Bearer token" + 'Content-Type': 'application/json', + Authorization: 'Bearer token' } - } as RequestInit); - }); - + } as RequestInit) + }) - it("Check undefined body", async () => { - global.fetch = jest.fn(() => successfulResponse); + it('Check undefined body', async () => { + global.fetch = jest.fn(() => successfulResponse) - const data = await http<{ succes: true }>(httpArguments); + const data = await http<{ succes: true }>(httpArguments) - expect(fetch).toHaveBeenCalledWith("/test", { - method: "POST", + expect(fetch).toHaveBeenCalledWith('/test', { + method: 'POST', body: JSON.stringify({ data: 123 }), headers: { - "Content-Type": "application/json", - Authorization: "Bearer token" + 'Content-Type': 'application/json', + Authorization: 'Bearer token' } - } as RequestInit); + } as RequestInit) - expect(data).toEqual({ succes: true }); - }); + expect(data).toEqual({ succes: true }) + }) - it("Should return a successful response", async () => { - global.fetch = jest.fn(() => successfulResponse); + it('Should return a successful response', async () => { + global.fetch = jest.fn(() => successfulResponse) - const data = http<{ succes: true }>(httpArguments); + const data = http<{ succes: true }>(httpArguments) - expect(data).resolves.toEqual({ succes: true }); - }); + expect(data).resolves.toEqual({ succes: true }) + }) - it("Should return a ko response", async () => { - global.fetch = jest.fn(() => responseKo); + it('Should return a ko response', async () => { + global.fetch = jest.fn(() => responseKo) - const data = http<{ succes: true }>(httpArguments); + const data = http<{ succes: true }>(httpArguments) expect(data).rejects.toBeTruthy() - }); + }) - it("Should return an error", async () => { - global.fetch = jest.fn(() => responseError); + it('Should return an error', async () => { + global.fetch = jest.fn(() => responseError) - const data = http<{ succes: true }>(httpArguments); + const data = http<{ succes: true }>(httpArguments) expect(data).rejects.toEqual({ status: 500, - message: "error" - }); - }); -}); + message: 'error' + }) + }) +}) diff --git a/src/test/utils/sort-search.spec.ts b/src/test/utils/sort-search.spec.ts index 2ddafcb..d35fefc 100644 --- a/src/test/utils/sort-search.spec.ts +++ b/src/test/utils/sort-search.spec.ts @@ -1,9 +1,12 @@ -import { objectKeysToLower } from "../../utils" +import { objectKeysToLower } from '../../utils' describe('Test sort-search utilities', () => { - - it('objectKeysToLower', () => { - const obj = {"SFYT": 1213, "HVjlp": "TYTY", Tasd: {}} - expect(objectKeysToLower(obj)).toEqual({"sfyt": 1213, "hvjlp": "TYTY", tasd: {}}) + it('objectKeysToLower', () => { + const obj = { SFYT: 1213, HVjlp: 'TYTY', Tasd: {} } + expect(objectKeysToLower(obj)).toEqual({ + sfyt: 1213, + hvjlp: 'TYTY', + tasd: {} }) -}) \ No newline at end of file + }) +}) diff --git a/src/types/http.types.ts b/src/types/http.types.ts index a056b2b..1aa00d7 100644 --- a/src/types/http.types.ts +++ b/src/types/http.types.ts @@ -1,7 +1,7 @@ export type HttpMethod = 'POST' | 'GET' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'PUT' export interface HttpOptions { - url: string; - method: HttpMethod; - data?: any; - headers?: Record + url: string + method: HttpMethod + data?: any + headers?: Record } diff --git a/src/types/i18npro.types.ts b/src/types/i18npro.types.ts index 4d07e2e..fa1bc09 100644 --- a/src/types/i18npro.types.ts +++ b/src/types/i18npro.types.ts @@ -1,20 +1,17 @@ export interface I18Message { - [key: string]: string | number; + [key: string]: string | number } export interface I18Dictionary { - [key: string]: I18Message; + [key: string]: I18Message } export interface DynamicData { - [key: string]: string; + [key: string]: string } export interface ChangeLanguage { ( locale: string, messages: string | Record - ): Promise; - ( - locale: string, - apiUrl: string - ): Promise; + ): Promise + (locale: string, apiUrl: string): Promise } diff --git a/src/types/index.ts b/src/types/index.ts index 8d5d5f1..ea4a5ca 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,10 +1,7 @@ export type { - DynamicData, - I18Message, - I18Dictionary, - ChangeLanguage + DynamicData, + I18Message, + I18Dictionary, + ChangeLanguage } from './i18npro.types' -export type { - HttpMethod, - HttpOptions -} from './http.types' \ No newline at end of file +export type { HttpMethod, HttpOptions } from './http.types' diff --git a/src/utils/http.ts b/src/utils/http.ts index 805a484..23284ec 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -1,18 +1,23 @@ -import { HttpOptions } from "../types" +import { HttpOptions } from '../types' /**@internal */ -export const http = ({method, url, data, headers}: HttpOptions): Promise => { - return fetch(url, { - body: data? JSON.stringify(data) : undefined, - method, - headers: { - "Content-Type": "application/json", - ...headers - } - }).then(res => { - if (res.ok){ - return res.json() as Promise - } - return Promise.reject(res) - }) +export const http = ({ + method, + url, + data, + headers +}: HttpOptions): Promise => { + return fetch(url, { + body: data ? JSON.stringify(data) : undefined, + method, + headers: { + 'Content-Type': 'application/json', + ...headers + } + }).then((res) => { + if (res.ok) { + return res.json() as Promise + } + return Promise.reject(res) + }) } diff --git a/src/utils/index.ts b/src/utils/index.ts index cdf1294..b0d8819 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,2 @@ -export { http } from "./http"; -export { objectKeysToLower } from "./sort-search"; +export { http } from './http' +export { objectKeysToLower } from './sort-search' diff --git a/src/utils/sort-search.ts b/src/utils/sort-search.ts index 8d3b3c1..5207cda 100644 --- a/src/utils/sort-search.ts +++ b/src/utils/sort-search.ts @@ -1,7 +1,7 @@ export const objectKeysToLower = (obj: T): T => { const mappedObject: [string, any][] = Object.entries(obj).map( ([key, val]) => [key.toLowerCase(), val] - ); - const sortedKeys = new Map([...mappedObject]); - return Object.fromEntries(sortedKeys) as T; -}; + ) + const sortedKeys = new Map([...mappedObject]) + return Object.fromEntries(sortedKeys) as T +}