diff --git a/build/config/i18next-scanner.config.js b/build/config/i18next-scanner.config.js index 96078b8d..b901c752 100644 --- a/build/config/i18next-scanner.config.js +++ b/build/config/i18next-scanner.config.js @@ -65,11 +65,18 @@ module.exports = { code, { component: 'Trans', i18nKey: 'i18nKey' }, (key, options) => { - // 如果不是手动给, 则使用defaultValue 作为key - if (key === '') { - key = options.defaultValue; - } - let hashKey = `k${crc32(key).toString(16)}`; + /** + * 处理scanner与react-i18next算法不一致导致的问题 + * Reference: https://github.com/i18next/i18next-scanner/issues/125 + */ + let sentence = options.defaultValue; + // remove surrounding interopations to match i18next simpilied result + // @see https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md#800 + sentence = sentence.replace(/<(\d+)>{{(\w+)}}<\/\1>/g, '{{$2}}'); + sentence = sentence.replace(/\s+/g, ' '); + options.defaultValue = sentence; + + const hashKey = `k${crc32(key || sentence).toString(16)}`; parser.set(hashKey, options); } ); diff --git a/shared/i18n/Trans.tsx b/shared/i18n/Trans.tsx new file mode 100644 index 00000000..5ecc9c05 --- /dev/null +++ b/shared/i18n/Trans.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Trans as OriginalTrans, TransProps } from 'react-i18next'; + +type Props = Omit, 't'>; +export const Trans: React.FC = React.memo((props) => { + return {props.children}; +}); +Trans.displayName = 'Trans'; diff --git a/shared/i18n/index.ts b/shared/i18n/index.ts index 7623cb98..2ddf6934 100644 --- a/shared/i18n/index.ts +++ b/shared/i18n/index.ts @@ -22,6 +22,13 @@ i18next console.log('缺少翻译:', ...args); }, }, + react: { + // Reference: https://react.i18next.com/latest/trans-component#i-18-next-options + hashTransKey(defaultValue: string) { + // return a key based on defaultValue or if you prefer to just remind you should set a key return false and throw an error + return `k${crc32(defaultValue).toString(16)}`; + }, + }, } as any); /** @@ -30,7 +37,7 @@ i18next export const t: TFunction = ( key: string, defaultValue?: string, - options?: TOptionsBase + options?: TOptionsBase & Record ) => { try { const hashKey = `k${crc32(key).toString(16)}`; diff --git a/shared/i18n/langs/en-US/translation.json b/shared/i18n/langs/en-US/translation.json index e7d201dc..9328ade4 100644 --- a/shared/i18n/langs/en-US/translation.json +++ b/shared/i18n/langs/en-US/translation.json @@ -23,6 +23,7 @@ "k3e514bd0": "Panel name cannot be empty", "k3f3597fc": "All", "k419da0ef": "Message explanation", + "k424be044": "This invite expired in <2>{{date}}", "k42a44318": "Joined", "k42a98418": "File Service", "k4603baea": "Create Group Panel", @@ -97,6 +98,7 @@ "kdd4c838c": "Jump to Group", "kdd6c18f8": "Service exception", "ke187440d": "Panel type cannot be empty", + "ked2baf28": "Loading...", "ked5385d5": "Create Panel", "keda14478": "You are the group manager, leaving the group will cause the group to be dissolved", "kef25594f": "Nickname#0000", diff --git a/shared/i18n/langs/zh-CN/translation.json b/shared/i18n/langs/zh-CN/translation.json index 3e9fee70..64b761f1 100644 --- a/shared/i18n/langs/zh-CN/translation.json +++ b/shared/i18n/langs/zh-CN/translation.json @@ -23,6 +23,7 @@ "k3e514bd0": "面板名不能为空", "k3f3597fc": "全员", "k419da0ef": "消息解释", + "k424be044": "该邀请将于 <2>{{date}} 过期", "k42a44318": "已加入", "k42a98418": "文件服务", "k4603baea": "创建群组面板", @@ -97,6 +98,7 @@ "kdd4c838c": "跳转到群组", "kdd6c18f8": "服务异常", "ke187440d": "面板类型不能为空", + "ked2baf28": "加载中...", "ked5385d5": "创建面板", "keda14478": "您是群组管理者,退出群组会导致解散群组", "kef25594f": "用户昵称#0000", diff --git a/shared/index.tsx b/shared/index.tsx index 71422120..5ab3c31b 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -35,6 +35,7 @@ export { TcProvider } from './components/Provider'; // i18n export { t, setLanguage, useTranslation } from './i18n'; +export { Trans } from './i18n/Trans'; // hooks export { useAsync } from './hooks/useAsync'; diff --git a/web/src/components/LoadingSpinner.tsx b/web/src/components/LoadingSpinner.tsx index 3c1352ea..e6a9c8f6 100644 --- a/web/src/components/LoadingSpinner.tsx +++ b/web/src/components/LoadingSpinner.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { t } from 'tailchat-shared'; import { Spinner } from './Spinner'; interface LoadingSpinnerProps { @@ -9,7 +10,7 @@ export const LoadingSpinner: React.FC = React.memo( return (
- {props.tip ?? 'Processing'} + {props.tip ?? t('加载中...')}
); } diff --git a/web/src/routes/Invite/InviteInfo.tsx b/web/src/routes/Invite/InviteInfo.tsx index 6b13313a..c6509144 100644 --- a/web/src/routes/Invite/InviteInfo.tsx +++ b/web/src/routes/Invite/InviteInfo.tsx @@ -8,6 +8,7 @@ import { getGroupBasicInfo, showErrorToasts, t, + Trans, useAsync, } from 'tailchat-shared'; @@ -67,9 +68,13 @@ export const InviteInfo: React.FC = React.memo((props) => { {value.expired && (
- 该邀请将于{' '} - {datetimeFromNow(value.expired)}{' '} - 过期 + + 该邀请将于{' '} + + {{ date: datetimeFromNow(value.expired) }} + {' '} + 过期 +
)}