From 9cecd67c508474f344402cbc7896f75d804ed83c Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 23 Jun 2023 20:10:13 +0800 Subject: [PATCH] half done --- packages/console/package.json | 4 +- .../src/components/CopyToClipboard/index.tsx | 4 +- .../src/components/DynamicT/index.test.tsx | 6 +- packages/core/package.json | 2 +- .../core/src/errors/RequestError/index.ts | 4 +- packages/core/src/include.d/i18next.d.ts | 9 ++ packages/core/src/oidc/init.ts | 3 +- packages/demo-app/package.json | 4 +- packages/phrases/src/index.ts | 4 +- packages/ui/package.json | 4 +- pnpm-lock.yaml | 91 ++++++++++--------- 11 files changed, 76 insertions(+), 59 deletions(-) create mode 100644 packages/core/src/include.d/i18next.d.ts diff --git a/packages/console/package.json b/packages/console/package.json index 7dbdeb95e..fac5e6d67 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -74,7 +74,7 @@ "dnd-core": "^16.0.0", "eslint": "^8.34.0", "history": "^5.3.0", - "i18next": "^22.4.15", + "i18next": "^23.2.3", "i18next-browser-languagedetector": "^7.0.1", "identity-obj-proxy": "^3.0.0", "jest": "^29.5.0", @@ -104,7 +104,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.43.9", "react-hot-toast": "^2.2.0", - "react-i18next": "^12.3.1", + "react-i18next": "^13.0.0", "react-markdown": "^8.0.0", "react-modal": "^3.15.1", "react-paginate": "^8.1.3", diff --git a/packages/console/src/components/CopyToClipboard/index.tsx b/packages/console/src/components/CopyToClipboard/index.tsx index 90728fed5..15ea4680a 100644 --- a/packages/console/src/components/CopyToClipboard/index.tsx +++ b/packages/console/src/components/CopyToClipboard/index.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import type { TFuncKey } from 'i18next'; +import type { ParseKeys } from 'i18next'; import type { MouseEventHandler } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -23,7 +23,7 @@ type Props = { isWordWrapAllowed?: boolean; }; -type CopyState = TFuncKey<'translation', 'admin_console.general'>; +type CopyState = ParseKeys<'translation', {}, 'admin_console.general'>; function CopyToClipboard({ value, diff --git a/packages/console/src/components/DynamicT/index.test.tsx b/packages/console/src/components/DynamicT/index.test.tsx index 10a51ace3..e9e9be8ff 100644 --- a/packages/console/src/components/DynamicT/index.test.tsx +++ b/packages/console/src/components/DynamicT/index.test.tsx @@ -1,18 +1,18 @@ import { render } from '@testing-library/react'; -import { t, type TFuncKey, type TypeOptions } from 'i18next'; +import { t, type ParseKeys } from 'i18next'; import DynamicT from '.'; describe('', () => { it('should render a correct key', () => { - const key: TFuncKey = 'general.add'; + const key: ParseKeys<'translation', Record, 'admin_console'> = 'general.add'; const { container } = render(); expect(container.innerHTML).toBe(t(`admin_console.${key}`)); }); it('should render an error message for a non-leaf key', () => { - const key: TFuncKey = 'general'; + const key: ParseKeys<'translation', Record, 'admin_console'> = 'general'; const { container } = render(); expect(container.innerHTML).toBe( diff --git a/packages/core/package.json b/packages/core/package.json index 58008c5af..01359efb4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,7 +52,7 @@ "got": "^13.0.0", "hash-wasm": "^4.9.0", "helmet": "^7.0.0", - "i18next": "^22.4.15", + "i18next": "^23.2.3", "iconv-lite": "0.6.3", "jose": "^4.11.0", "koa": "^2.13.1", diff --git a/packages/core/src/errors/RequestError/index.ts b/packages/core/src/errors/RequestError/index.ts index 8a374ff11..29ae183bd 100644 --- a/packages/core/src/errors/RequestError/index.ts +++ b/packages/core/src/errors/RequestError/index.ts @@ -1,4 +1,4 @@ -import type { LogtoErrorCode, LogtoErrorI18nKey } from '@logto/phrases'; +import type { LogtoErrorCode } from '@logto/phrases'; import type { RequestErrorBody, RequestErrorMetadata } from '@logto/schemas'; import type { Optional } from '@silverhand/essentials'; import { conditional, pick } from '@silverhand/essentials'; @@ -29,7 +29,7 @@ export default class RequestError extends Error { expose = true, ...interpolation } = typeof input === 'string' ? { code: input } : input; - const message = i18next.t(`errors:${code}`, interpolation); + const message = i18next.t(`errors:${code}`, interpolation); super(message); diff --git a/packages/core/src/include.d/i18next.d.ts b/packages/core/src/include.d/i18next.d.ts new file mode 100644 index 000000000..d83c6ded4 --- /dev/null +++ b/packages/core/src/include.d/i18next.d.ts @@ -0,0 +1,9 @@ +// https://react.i18next.com/latest/typescript#create-a-declaration-file + +import type { LocalePhrase } from '@logto/phrases'; + +declare module 'i18next' { + interface CustomTypeOptions { + resources: LocalePhrase; + } +} diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index fc3b3486f..71525db7c 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -3,7 +3,6 @@ import { readFileSync } from 'node:fs'; import { userClaims } from '@logto/core-kit'; -import type { I18nKey } from '@logto/phrases'; import { customClientMetadataDefault, CustomClientMetadataKey, @@ -95,7 +94,7 @@ export default function initOidc( ctx.body = logoutSuccessSource.replace( // eslint-disable-next-line no-template-curly-in-string '${message}', - i18next.t('oidc.logout_success') + i18next.t('oidc.logout_success') ); }, }, diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index b5ebf093a..bfeb0b6c1 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -35,7 +35,7 @@ "buffer": "^5.7.1", "cross-env": "^7.0.3", "eslint": "^8.34.0", - "i18next": "^22.4.15", + "i18next": "^23.2.3", "i18next-browser-languagedetector": "^7.0.1", "lint-staged": "^13.0.0", "parcel": "2.9.2", @@ -43,7 +43,7 @@ "prettier": "^2.8.2", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-i18next": "^12.3.1", + "react-i18next": "^13.0.0", "stylelint": "^15.0.0", "typescript": "^5.0.0", "zod": "^3.20.2" diff --git a/packages/phrases/src/index.ts b/packages/phrases/src/index.ts index 5e5e142e4..39796212d 100644 --- a/packages/phrases/src/index.ts +++ b/packages/phrases/src/index.ts @@ -52,7 +52,9 @@ export const builtInLanguageTagGuard = z.enum(builtInLanguages); export type BuiltInLanguageTag = z.infer; export type Errors = typeof en.errors; -export type LogtoErrorCode = NormalizeKeyPaths; +// Explicitly exclude the first-level keys of `Errors` to prevent invalid keys from being used. +// Maybe we can use a more elegant way to do this. +export type LogtoErrorCode = Exclude, keyof Errors>; export type LogtoErrorI18nKey = `errors:${LogtoErrorCode}`; export type AdminConsoleKey = NormalizeKeyPaths; diff --git a/packages/ui/package.json b/packages/ui/package.json index 8f988ad8b..dbc360656 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -55,7 +55,7 @@ "color": "^4.2.3", "cross-env": "^7.0.3", "eslint": "^8.34.0", - "i18next": "^22.4.15", + "i18next": "^23.2.3", "i18next-browser-languagedetector": "^7.0.1", "identity-obj-proxy": "^3.0.0", "jest": "^29.5.0", @@ -76,7 +76,7 @@ "react-dom": "^18.0.0", "react-helmet": "^6.1.0", "react-hook-form": "^7.34.0", - "react-i18next": "^12.3.1", + "react-i18next": "^13.0.0", "react-modal": "^3.15.1", "react-router-dom": "^6.10.0", "react-string-replace": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adf36693f..1b5a193f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -2920,8 +2920,8 @@ importers: specifier: ^5.3.0 version: 5.3.0 i18next: - specifier: ^22.4.15 - version: 22.4.15 + specifier: ^23.2.3 + version: 23.2.3 i18next-browser-languagedetector: specifier: ^7.0.1 version: 7.0.1 @@ -3010,8 +3010,8 @@ importers: specifier: ^2.2.0 version: 2.2.0(csstype@3.0.11)(react-dom@18.2.0)(react@18.2.0) react-i18next: - specifier: ^12.3.1 - version: 12.3.1(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.0.0 + version: 13.0.0(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0) react-markdown: specifier: ^8.0.0 version: 8.0.0(@types/react@18.0.31)(react@18.2.0) @@ -3136,8 +3136,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 i18next: - specifier: ^22.4.15 - version: 22.4.15 + specifier: ^23.2.3 + version: 23.2.3 iconv-lite: specifier: 0.6.3 version: 0.6.3 @@ -3359,8 +3359,8 @@ importers: specifier: ^8.34.0 version: 8.34.0 i18next: - specifier: ^22.4.15 - version: 22.4.15 + specifier: ^23.2.3 + version: 23.2.3 i18next-browser-languagedetector: specifier: ^7.0.1 version: 7.0.1 @@ -3383,8 +3383,8 @@ importers: specifier: ^18.0.0 version: 18.2.0(react@18.2.0) react-i18next: - specifier: ^12.3.1 - version: 12.3.1(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.0.0 + version: 13.0.0(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0) stylelint: specifier: ^15.0.0 version: 15.0.0 @@ -3935,8 +3935,8 @@ importers: specifier: ^8.34.0 version: 8.34.0 i18next: - specifier: ^22.4.15 - version: 22.4.15 + specifier: ^23.2.3 + version: 23.2.3 i18next-browser-languagedetector: specifier: ^7.0.1 version: 7.0.1 @@ -3998,8 +3998,8 @@ importers: specifier: ^7.34.0 version: 7.34.0(react@18.2.0) react-i18next: - specifier: ^12.3.1 - version: 12.3.1(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.0.0 + version: 13.0.0(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0) react-modal: specifier: ^3.15.1 version: 3.15.1(react-dom@18.2.0)(react@18.2.0) @@ -6207,6 +6207,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 + dev: true + + /@babel/runtime@7.22.5: + resolution: {integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 /@babel/template@7.18.10: resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} @@ -6250,7 +6257,7 @@ packages: /@changesets/apply-release-plan@6.1.1: resolution: {integrity: sha512-LaQiP/Wf0zMVR0HNrLQAjz3rsNsr0d/RlnP6Ef4oi8VafOwnY1EoWdK4kssuUJGgNgDyHpomS50dm8CU3D7k7g==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/config': 2.2.0 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 1.5.0 @@ -6268,7 +6275,7 @@ packages: /@changesets/assemble-release-plan@5.2.2: resolution: {integrity: sha512-B1qxErQd85AeZgZFZw2bDKyOfdXHhG+X5S+W3Da2yCem8l/pRy4G/S7iOpEcMwg6lH8q2ZhgbZZwZ817D+aLuQ==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/errors': 0.1.4 '@changesets/get-dependents-graph': 1.3.4 '@changesets/types': 5.2.0 @@ -6352,7 +6359,7 @@ packages: /@changesets/get-release-plan@3.0.15: resolution: {integrity: sha512-W1tFwxE178/en+zSj/Nqbc3mvz88mcdqUMJhRzN1jDYqN3QI4ifVaRF9mcWUU+KI0gyYEtYR65tour690PqTcA==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/assemble-release-plan': 5.2.2 '@changesets/config': 2.2.0 '@changesets/pre': 1.0.13 @@ -6368,7 +6375,7 @@ packages: /@changesets/git@1.5.0: resolution: {integrity: sha512-Xo8AT2G7rQJSwV87c8PwMm6BAc98BnufRMsML7m7Iw8Or18WFvFmxqG5aOL5PBvhgq9KrKvaeIBNIymracSuHg==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.0 '@manypkg/get-packages': 1.1.3 @@ -6392,7 +6399,7 @@ packages: /@changesets/pre@1.0.13: resolution: {integrity: sha512-jrZc766+kGZHDukjKhpBXhBJjVQMied4Fu076y9guY1D3H622NOw8AQaLV3oQsDtKBTrT2AUFjt9Z2Y9Qx+GfA==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.0 '@manypkg/get-packages': 1.1.3 @@ -6402,7 +6409,7 @@ packages: /@changesets/read@0.5.8: resolution: {integrity: sha512-eYaNfxemgX7f7ELC58e7yqQICW5FB7V+bd1lKt7g57mxUrTveYME+JPaBPpYx02nP53XI6CQp6YxnR9NfmFPKw==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/git': 1.5.0 '@changesets/logger': 0.0.5 '@changesets/parse': 0.3.15 @@ -6423,7 +6430,7 @@ packages: /@changesets/write@0.2.1: resolution: {integrity: sha512-KUd49nt2fnYdGixIqTi1yVE1nAoZYUMdtB3jBfp77IMqjZ65hrmZE5HdccDlTeClZN0420ffpnfET3zzeY8pdw==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/types': 5.2.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -7221,7 +7228,7 @@ packages: /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 @@ -7230,7 +7237,7 @@ packages: /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -9079,7 +9086,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@types/aria-query': 5.0.1 aria-query: 5.0.0 chalk: 4.1.2 @@ -10066,7 +10073,7 @@ packages: resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} engines: {node: '>=6.0'} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 '@babel/runtime-corejs3': 7.19.4 dev: true @@ -10249,7 +10256,7 @@ packages: /babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 cosmiconfig: 6.0.0 resolve: 1.22.1 dev: false @@ -11524,7 +11531,7 @@ packages: /dom-helpers@3.4.0: resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 dev: true /dom-serializer@1.4.1: @@ -11947,7 +11954,7 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - '@babel/runtime': 7.19.4 + '@babel/runtime': 7.22.5 aria-query: 4.2.2 array-includes: 3.1.6 ast-types-flow: 0.0.7 @@ -13362,10 +13369,10 @@ packages: '@babel/runtime': 7.21.0 dev: true - /i18next@22.4.15: - resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==} + /i18next@23.2.3: + resolution: {integrity: sha512-5spO7L0rNmW0jFuNhz+gfirlFt1anle4mTy4+gFkgsH0+T3R5++4oncBrzeKa7v8pweRyGBoGmOpboqlxovg6A==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -17463,10 +17470,10 @@ packages: - csstype dev: true - /react-i18next@12.3.1(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==} + /react-i18next@13.0.0(i18next@23.2.3)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qRFbrSgynsBSjfnSTb/Um3mw9uPjOfDi4Iq2rMCuzfsRsYGdkEdyCr0i+T0bR0bG6xwULvK4k1oRVLLd7ZDBVw==} peerDependencies: - i18next: '>= 19.0.0' + i18next: '>= 23.0.1' react: '>= 16.8.0 || ^18.0.0' react-dom: '*' react-native: '*' @@ -17476,9 +17483,9 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 html-parse-stringify: 3.0.1 - i18next: 22.4.15 + i18next: 23.2.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -17800,7 +17807,7 @@ packages: /redux@4.1.2: resolution: {integrity: sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.5 dev: true /refractor@3.6.0: @@ -18075,7 +18082,7 @@ packages: filesize: 10.0.7 gzip-size: 7.0.0 rollup: 3.8.0 - terser: 5.17.7 + terser: 5.18.1 dev: true /rollup@3.8.0: @@ -19209,8 +19216,8 @@ packages: engines: {node: '>=8'} dev: true - /terser@5.17.7: - resolution: {integrity: sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==} + /terser@5.18.1: + resolution: {integrity: sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==} engines: {node: '>=10'} hasBin: true dependencies: @@ -19841,7 +19848,7 @@ packages: dev: true /void-elements@3.1.0: - resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=} + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} dev: true