diff --git a/packages/phrases-ui/README.md b/packages/phrases-ui/README.md new file mode 100644 index 000000000..b7e2532dd --- /dev/null +++ b/packages/phrases-ui/README.md @@ -0,0 +1,11 @@ +# `@logto/phrases-ui` + +> TODO: description + +## Usage + +``` +const uiPhrases = require('@logto/ui-phrases'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/phrases-ui/package.json b/packages/phrases-ui/package.json new file mode 100644 index 000000000..367290474 --- /dev/null +++ b/packages/phrases-ui/package.json @@ -0,0 +1,45 @@ +{ + "name": "@logto/phrases-ui", + "version": "0.1.0", + "description": "i18n phrases for main-flow", + "author": "Silverhand Inc. ", + "homepage": "https://github.com/logto-io/logto#readme", + "license": "MPL-2.0", + "main": "lib/index.js", + "private": true, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/logto-io/logto.git" + }, + "scripts": { + "precommit": "lint-staged", + "build": "rm -rf lib/ && tsc", + "lint": "eslint --ext .ts src", + "lint:report": "pnpm lint --format json --output-file report.json", + "prepack": "pnpm build" + }, + "bugs": { + "url": "https://github.com/logto-io/logto/issues" + }, + "dependencies": { + "@silverhand/essentials": "^1.1.4" + }, + "devDependencies": { + "@silverhand/eslint-config": "^0.14.0", + "@silverhand/ts-config": "^0.14.0", + "eslint": "^8.10.0", + "lint-staged": "^13.0.0", + "prettier": "^2.3.2", + "typescript": "^4.6.2" + }, + "eslintConfig": { + "extends": "@silverhand" + }, + "prettier": "@silverhand/eslint-config/.prettierrc" +} diff --git a/packages/phrases-ui/src/index.ts b/packages/phrases-ui/src/index.ts new file mode 100644 index 000000000..ce5a7979b --- /dev/null +++ b/packages/phrases-ui/src/index.ts @@ -0,0 +1,16 @@ +import { NormalizeKeyPaths } from '@silverhand/essentials'; + +import en from './locales/en'; +import zhCN from './locales/zh-cn'; +import { Resource, Language } from './types'; + +export { Language } from './types'; + +export type I18nKey = NormalizeKeyPaths; + +const resource: Resource = { + [Language.English]: en, + [Language.Chinese]: zhCN, +}; + +export default resource; diff --git a/packages/phrases-ui/src/locales/en.ts b/packages/phrases-ui/src/locales/en.ts new file mode 100644 index 000000000..77a541561 --- /dev/null +++ b/packages/phrases-ui/src/locales/en.ts @@ -0,0 +1,79 @@ +const translation = { + input: { + username: 'Username', + password: 'Password', + email: 'Email', + phone_number: 'Phone number', + confirm_password: 'Confirm password', + }, + secondary: { + sign_in_with: 'Sign in with {{methods, list(type: disjunction;)}}', + social_bind_with: + 'Already have an account? Sign in to bind {{methods, list(type: disjunction;)}} with your social identity.', + }, + action: { + sign_in: 'Sign In', + continue: 'Continue', + create_account: 'Create Account', + create: 'Create', + enter_passcode: 'Enter Passcode', + confirm: 'Confirm', + cancel: 'Cancel', + bind: 'Binding with {{address}}', + back: 'Go Back', + nav_back: 'Back', + agree: 'Agree', + got_it: 'Got it', + sign_in_with: 'Sign in with {{name}}', + }, + description: { + email: 'email', + phone: 'phone', + phone_number: 'phone number', + reminder: 'Reminder', + not_found: '404 Not Found', + agree_with_terms: 'I have read and agree to the ', + agree_with_terms_modal: 'Please read the {{terms}} and then agree the box first.', + terms_of_use: 'Terms of Use', + create_account: 'Create Account', + forgot_password: 'Forgot Password?', + or: 'or', + enter_passcode: 'The passcode has been sent to your {{address}}', + passcode_sent: 'The passcode has been resent', + resend_after_seconds: 'Resend after {{seconds}} seconds', + resend_passcode: 'Resend Passcode', + continue_with: 'Continue with', + create_account_id_exists: + 'The account with {{type}} {{value}} already exists, would you like to sign in?', + sign_in_id_does_not_exists: + 'The account with {{type}} {{value}} does not exist, would you like to create a new account?', + bind_account_title: 'Binding Logto account', + social_create_account: 'No account? You can create a new account and bind.', + social_bind_account: 'Already have an account? Sign in to bind it with your social identity.', + social_bind_with_existing: 'We find a related account, you can bind it directly.', + }, + error: { + username_password_mismatch: 'Username and password do not match.', + username_required: 'Username is required.', + password_required: 'Password is required.', + username_exists: 'Username already exists.', + username_should_not_start_with_number: 'Username should not start with a number.', + username_valid_charset: 'Username should only contain letters, numbers, or underscore.', + invalid_email: 'The email is invalid', + invalid_phone: 'The phone number is invalid', + password_min_length: 'Password requires a minimum of {{min}} characters.', + passwords_do_not_match: 'Passwords do not match.', + agree_terms_required: 'You must agree to the Terms of Use before continuing.', + invalid_passcode: 'The passcode is invalid.', + invalid_connector_auth: 'The authorization is invalid.', + invalid_connector_request: 'The connector data is invalid.', + unknown: 'Unknown error, please try again later.', + invalid_session: 'Session not found. Please go back and sign in again.', + }, +}; + +const en = Object.freeze({ + translation, +}); + +export default en; diff --git a/packages/phrases-ui/src/locales/zh-cn.ts b/packages/phrases-ui/src/locales/zh-cn.ts new file mode 100644 index 000000000..ce4e8f067 --- /dev/null +++ b/packages/phrases-ui/src/locales/zh-cn.ts @@ -0,0 +1,79 @@ +import en from './en'; + +const translation = { + input: { + username: '用户名', + password: '密码', + email: '邮箱', + phone_number: '手机号', + confirm_password: '确认密码', + }, + secondary: { + sign_in_with: '通过 {{methods, list(type: disjunction;), zhOrSpaces}} 登录', + social_bind_with: + '绑定到已有账户? 使用 {{methods, list(type: disjunction;), zhOrSpaces}} 登录并绑定。', + }, + action: { + sign_in: '登录', + continue: '继续', + create_account: '创建账号', + create: '创建', + enter_passcode: '输入验证码', + cancel: '取消', + confirm: '确认', + bind: '绑定到 {{address}}', + back: '返回', + nav_back: '返回', + agree: '同意', + got_it: '知道了', + sign_in_with: '通过 {{name}} 登录', + }, + description: { + email: '邮箱', + phone: '手机', + phone_number: '手机', + reminder: '提示', + not_found: '404 页面不存在', + agree_with_terms: '我已阅读并同意 ', + agree_with_terms_modal: 'Please read the {{terms}} and then agree the box first.', + terms_of_use: '使用条款', + create_account: '创建账号', + forgot_password: '忘记密码?', + or: '或', + enter_passcode: '验证码已经发送至您的{{ address }}', + passcode_sent: '验证码已经发送', + resend_after_seconds: '在 {{ seconds }} 秒后重发', + resend_passcode: '重发验证码', + continue_with: '通过以下方式继续', + create_account_id_exists: '{{type}}为 {{ value }} 的账号已存在,您要登录吗?', + sign_in_id_does_not_exists: '{{type}}为 {{ value }} 的账号不存在,您要创建一个新账号吗?', + bind_account_title: '绑定 Logto 账号', + social_create_account: 'No account? You can create a new account and bind.', + social_bind_account: 'Already have an account? Sign in to bind it with your social identity.', + social_bind_with_existing: 'We find a related account, you can bind it directly.', + }, + error: { + username_password_mismatch: '用户名和密码不匹配。', + username_required: '用户名必填', + password_required: '密码必填', + username_exists: '用户名已存在。', + username_should_not_start_with_number: '用户名不能以数字开头。', + username_valid_charset: '用户名只能包含英文字母、数字或下划线。', + invalid_email: '无效的邮箱。', + invalid_phone: '无效的手机号。', + password_min_length: '密码最少需要{{min}}个字符。', + passwords_do_not_match: '密码不匹配。', + agree_terms_required: '你需要同意使用条款以继续。', + invalid_passcode: '无效的验证码。', + invalid_connector_auth: '登录失败。', + invalid_connector_request: '无效的登录请求。', + unknown: '未知错误,请稍后重试。', + invalid_session: '未找到有效的会话,请重新登录。', + }, +}; + +const zhCN: typeof en = Object.freeze({ + translation, +}); + +export default zhCN; diff --git a/packages/phrases-ui/src/types.ts b/packages/phrases-ui/src/types.ts new file mode 100644 index 000000000..52c6554a3 --- /dev/null +++ b/packages/phrases-ui/src/types.ts @@ -0,0 +1,11 @@ +/* Copied from i18next/index.d.ts */ +export type Resource = Record; + +export type ResourceLanguage = Record; + +export type ResourceKey = string | Record; + +export enum Language { + English = 'en', + Chinese = 'zh-CN', +} diff --git a/packages/phrases-ui/tsconfig.json b/packages/phrases-ui/tsconfig.json new file mode 100644 index 000000000..22287725e --- /dev/null +++ b/packages/phrases-ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@silverhand/ts-config/tsconfig.base", + "compilerOptions": { + "outDir": "lib", + "declaration": true + }, + "include": ["src"] +} diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index 5f1b59986..3445c7917 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -20,79 +20,6 @@ const translation = { set_up: 'Set up', customize: 'Customize', }, - main_flow: { - input: { - username: 'Username', - password: 'Password', - email: 'Email', - phone_number: 'Phone number', - confirm_password: 'Confirm password', - }, - secondary: { - sign_in_with: 'Sign in with {{methods, list(type: disjunction;)}}', - social_bind_with: - 'Already have an account? Sign in to bind {{methods, list(type: disjunction;)}} with your social identity.', - }, - action: { - sign_in: 'Sign In', - continue: 'Continue', - create_account: 'Create Account', - create: 'Create', - enter_passcode: 'Enter Passcode', - confirm: 'Confirm', - cancel: 'Cancel', - bind: 'Binding with {{address}}', - back: 'Go Back', - nav_back: 'Back', - agree: 'Agree', - got_it: 'Got it', - sign_in_with: 'Sign in with {{name}}', - }, - description: { - email: 'email', - phone: 'phone', - phone_number: 'phone number', - reminder: 'Reminder', - not_found: '404 Not Found', - agree_with_terms: 'I have read and agree to the ', - agree_with_terms_modal: 'Please read the {{terms}} and then agree the box first.', - terms_of_use: 'Terms of Use', - create_account: 'Create Account', - forgot_password: 'Forgot Password?', - or: 'or', - enter_passcode: 'The passcode has been sent to your {{address}}', - passcode_sent: 'The passcode has been resent', - resend_after_seconds: 'Resend after {{seconds}} seconds', - resend_passcode: 'Resend Passcode', - continue_with: 'Continue with', - create_account_id_exists: - 'The account with {{type}} {{value}} already exists, would you like to sign in?', - sign_in_id_does_not_exists: - 'The account with {{type}} {{value}} does not exist, would you like to create a new account?', - bind_account_title: 'Binding Logto account', - social_create_account: 'No account? You can create a new account and bind.', - social_bind_account: 'Already have an account? Sign in to bind it with your social identity.', - social_bind_with_existing: 'We find a related account, you can bind it directly.', - }, - error: { - username_password_mismatch: 'Username and password do not match.', - username_required: 'Username is required.', - password_required: 'Password is required.', - username_exists: 'Username already exists.', - username_should_not_start_with_number: 'Username should not start with a number.', - username_valid_charset: 'Username should only contain letters, numbers, or underscore.', - invalid_email: 'The email is invalid', - invalid_phone: 'The phone number is invalid', - password_min_length: 'Password requires a minimum of {{min}} characters.', - passwords_do_not_match: 'Passwords do not match.', - agree_terms_required: 'You must agree to the Terms of Use before continuing.', - invalid_passcode: 'The passcode is invalid.', - invalid_connector_auth: 'The authorization is invalid.', - invalid_connector_request: 'The connector data is invalid.', - unknown: 'Unknown error, please try again later.', - invalid_session: 'Session not found. Please go back and sign in again.', - }, - }, admin_console: { title: 'Admin Console', sign_out: 'Sign out', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index a8e262cc6..2425ad4a7 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -22,77 +22,6 @@ const translation = { set_up: '配置', customize: '自定义', }, - main_flow: { - input: { - username: '用户名', - password: '密码', - email: '邮箱', - phone_number: '手机号', - confirm_password: '确认密码', - }, - secondary: { - sign_in_with: '通过 {{methods, list(type: disjunction;), zhOrSpaces}} 登录', - social_bind_with: - '绑定到已有账户? 使用 {{methods, list(type: disjunction;), zhOrSpaces}} 登录并绑定。', - }, - action: { - sign_in: '登录', - continue: '继续', - create_account: '创建账号', - create: '创建', - enter_passcode: '输入验证码', - cancel: '取消', - confirm: '确认', - bind: '绑定到 {{address}}', - back: '返回', - nav_back: '返回', - agree: '同意', - got_it: '知道了', - sign_in_with: '通过 {{name}} 登录', - }, - description: { - email: '邮箱', - phone: '手机', - phone_number: '手机', - reminder: '提示', - not_found: '404 页面不存在', - agree_with_terms: '我已阅读并同意 ', - agree_with_terms_modal: 'Please read the {{terms}} and then agree the box first.', - terms_of_use: '使用条款', - create_account: '创建账号', - forgot_password: '忘记密码?', - or: '或', - enter_passcode: '验证码已经发送至您的{{ address }}', - passcode_sent: '验证码已经发送', - resend_after_seconds: '在 {{ seconds }} 秒后重发', - resend_passcode: '重发验证码', - continue_with: '通过以下方式继续', - create_account_id_exists: '{{type}}为 {{ value }} 的账号已存在,您要登录吗?', - sign_in_id_does_not_exists: '{{type}}为 {{ value }} 的账号不存在,您要创建一个新账号吗?', - bind_account_title: '绑定 Logto 账号', - social_create_account: 'No account? You can create a new account and bind.', - social_bind_account: 'Already have an account? Sign in to bind it with your social identity.', - social_bind_with_existing: 'We find a related account, you can bind it directly.', - }, - error: { - username_password_mismatch: '用户名和密码不匹配。', - username_required: '用户名必填', - password_required: '密码必填', - username_exists: '用户名已存在。', - username_should_not_start_with_number: '用户名不能以数字开头。', - username_valid_charset: '用户名只能包含英文字母、数字或下划线。', - invalid_email: '无效的邮箱。', - invalid_phone: '无效的手机号。', - password_min_length: '密码最少需要{{min}}个字符。', - passwords_do_not_match: '密码不匹配。', - agree_terms_required: '你需要同意使用条款以继续。', - invalid_passcode: '无效的验证码。', - invalid_connector_auth: '登录失败。', - invalid_connector_request: '无效的登录请求。', - unknown: '未知错误,请稍后重试。', - invalid_session: '未找到有效的会话,请重新登录。', - }, - }, admin_console: { title: '管理面板', sign_out: '登出', diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 9314d7d6c..c87d7752b 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -47,6 +47,7 @@ "@logto/connector-types": "^0.1.0", "@logto/phrases": "^0.1.0", "@logto/shared": "^0.1.0", + "@logto/phrases-ui": "^0.1.0", "zod": "^3.14.3" } } diff --git a/packages/schemas/src/api/error.ts b/packages/schemas/src/api/error.ts index d8ddc0228..2229d96ad 100644 --- a/packages/schemas/src/api/error.ts +++ b/packages/schemas/src/api/error.ts @@ -1,4 +1,4 @@ -import { LogtoErrorCode } from '@logto/phrases'; +import type { LogtoErrorCode } from '@logto/phrases'; export type RequestErrorMetadata = Record & { code: LogtoErrorCode; diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 3d1e747bc..3105c69fe 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -1,4 +1,4 @@ -import { Language } from '@logto/phrases'; +import { Language } from '@logto/phrases-ui'; import { hexColorRegEx } from '@logto/shared'; import { z } from 'zod'; diff --git a/packages/schemas/src/seeds/sign-in-experience.ts b/packages/schemas/src/seeds/sign-in-experience.ts index 38c5a97a0..398ab6cef 100644 --- a/packages/schemas/src/seeds/sign-in-experience.ts +++ b/packages/schemas/src/seeds/sign-in-experience.ts @@ -1,4 +1,4 @@ -import { Language } from '@logto/phrases'; +import { Language } from '@logto/phrases-ui'; import { CreateSignInExperience, SignInMode } from '../db-entries'; import { BrandingStyle, SignInMethodState } from '../foundations'; diff --git a/packages/ui/package.json b/packages/ui/package.json index 6dddb9333..a52d705d0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -8,7 +8,7 @@ "start": "parcel src/index.html", "dev": "cross-env PORT=5001 parcel src/index.html --no-cache --hmr-port 6001", "check": "tsc --noEmit", - "build": "pnpm check && rm -rf dist && parcel build src/index.html --no-autoinstall --no-cache", + "build": "pnpm check && rm -rf dist && parcel build src/index.html --no-autoinstall --no-cache --detailed-report", "lint": "eslint --ext .ts --ext .tsx src", "lint:report": "pnpm lint --format json --output-file report.json", "stylelint": "stylelint \"src/**/*.scss\"", @@ -18,6 +18,7 @@ "devDependencies": { "@logto/phrases": "^0.1.0", "@logto/schemas": "^0.1.0", + "@logto/phrases-ui": "^0.1.0", "@parcel/core": "2.6.2", "@parcel/transformer-sass": "2.6.2", "@parcel/transformer-svg-react": "2.6.2", diff --git a/packages/ui/src/__mocks__/logto.tsx b/packages/ui/src/__mocks__/logto.tsx index 04792c2f7..61bc316e7 100644 --- a/packages/ui/src/__mocks__/logto.tsx +++ b/packages/ui/src/__mocks__/logto.tsx @@ -1,4 +1,4 @@ -import { Language } from '@logto/phrases'; +import { Language } from '@logto/phrases-ui'; import { BrandingStyle, ConnectorPlatform, diff --git a/packages/ui/src/components/Button/SocialLinkButton.tsx b/packages/ui/src/components/Button/SocialLinkButton.tsx index f401a7e70..788ae8809 100644 --- a/packages/ui/src/components/Button/SocialLinkButton.tsx +++ b/packages/ui/src/components/Button/SocialLinkButton.tsx @@ -20,7 +20,7 @@ const SocialLinkButton = ({ isDisabled, className, connector, onClick }: Props) const { t, i18n: { language }, - } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + } = useTranslation(); // TODO: LOG-2393 should fix name[locale] syntax error const foundName = Object.entries(name).find(([lang]) => lang === language); const localName = foundName ? foundName[1] : name.en; diff --git a/packages/ui/src/components/ConfirmModal/AcModal.tsx b/packages/ui/src/components/ConfirmModal/AcModal.tsx index 628ef8ce1..2369c0676 100644 --- a/packages/ui/src/components/ConfirmModal/AcModal.tsx +++ b/packages/ui/src/components/ConfirmModal/AcModal.tsx @@ -20,7 +20,7 @@ const AcModal = ({ onConfirm, onClose, }: ModalProps) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); return ( { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(true); return ( diff --git a/packages/ui/src/components/ConfirmModal/MobileModal.tsx b/packages/ui/src/components/ConfirmModal/MobileModal.tsx index 5cd5fc5d9..35c4fe879 100644 --- a/packages/ui/src/components/ConfirmModal/MobileModal.tsx +++ b/packages/ui/src/components/ConfirmModal/MobileModal.tsx @@ -18,7 +18,7 @@ const MobileModal = ({ onConfirm, onClose, }: ModalProps) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); return ( ; - confirmText?: TFuncKey<'translation', 'main_flow'>; + cancelText?: TFuncKey; + confirmText?: TFuncKey; onConfirm?: () => void; onClose: () => void; }; diff --git a/packages/ui/src/components/Divider/index.tsx b/packages/ui/src/components/Divider/index.tsx index 609ffd77a..ca92c6453 100644 --- a/packages/ui/src/components/Divider/index.tsx +++ b/packages/ui/src/components/Divider/index.tsx @@ -6,11 +6,11 @@ import * as styles from './index.module.scss'; type Props = { className?: string; - label?: TFuncKey<'translation', 'main_flow'>; + label?: TFuncKey; }; const Divider = ({ className, label }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); return (
diff --git a/packages/ui/src/components/ErrorMessage/index.tsx b/packages/ui/src/components/ErrorMessage/index.tsx index 69b90a1a4..28b996b8a 100644 --- a/packages/ui/src/components/ErrorMessage/index.tsx +++ b/packages/ui/src/components/ErrorMessage/index.tsx @@ -4,7 +4,7 @@ import { TFuncKey, useTranslation } from 'react-i18next'; import * as styles from './index.module.scss'; -type ErrorCode = TFuncKey<'translation', 'main_flow.error'>; +type ErrorCode = TFuncKey<'translation', 'error'>; export type ErrorType = ErrorCode | { code: ErrorCode; data?: Record }; @@ -15,7 +15,7 @@ export type Props = { }; const ErrorMessage = ({ error, className, children }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow.error' }); + const { t } = useTranslation(undefined, { keyPrefix: 'error' }); const getMessage = () => { if (!error) { diff --git a/packages/ui/src/components/NavBar/index.tsx b/packages/ui/src/components/NavBar/index.tsx index b486194a4..1a0d62258 100644 --- a/packages/ui/src/components/NavBar/index.tsx +++ b/packages/ui/src/components/NavBar/index.tsx @@ -12,7 +12,7 @@ type Props = { const NavBar = ({ title }: Props) => { const navigate = useNavigate(); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); return (
diff --git a/packages/ui/src/components/Notification/index.tsx b/packages/ui/src/components/Notification/index.tsx index 897364134..57b613ddc 100644 --- a/packages/ui/src/components/Notification/index.tsx +++ b/packages/ui/src/components/Notification/index.tsx @@ -13,7 +13,7 @@ type Props = { }; const Notification = ({ className, message, onClose }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); return (
diff --git a/packages/ui/src/components/TermsOfUse/index.test.tsx b/packages/ui/src/components/TermsOfUse/index.test.tsx index 9fb9b5572..f688f687a 100644 --- a/packages/ui/src/components/TermsOfUse/index.test.tsx +++ b/packages/ui/src/components/TermsOfUse/index.test.tsx @@ -7,7 +7,7 @@ import TermsOfUse from '.'; describe('Terms of Use', () => { const onChange = jest.fn(); const contentUrl = 'http://logto.dev/'; - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const prefix = t('description.agree_with_terms'); beforeEach(() => { diff --git a/packages/ui/src/components/TermsOfUse/index.tsx b/packages/ui/src/components/TermsOfUse/index.tsx index c29ac6284..6334c2769 100644 --- a/packages/ui/src/components/TermsOfUse/index.tsx +++ b/packages/ui/src/components/TermsOfUse/index.tsx @@ -17,7 +17,7 @@ type Props = { }; const TermsOfUse = ({ name, className, termsUrl, isChecked, onChange, onTermsClick }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const prefix = t('description.agree_with_terms'); diff --git a/packages/ui/src/components/TextLink/index.tsx b/packages/ui/src/components/TextLink/index.tsx index c006deed4..f349f7cc3 100644 --- a/packages/ui/src/components/TextLink/index.tsx +++ b/packages/ui/src/components/TextLink/index.tsx @@ -8,13 +8,13 @@ import * as styles from './index.module.scss'; export type Props = AnchorHTMLAttributes & { className?: string; children?: ReactNode; - text?: TFuncKey<'translation', 'main_flow'>; + text?: TFuncKey; type?: 'primary' | 'secondary'; to?: string; }; const TextLink = ({ className, children, text, type = 'primary', to, ...rest }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); if (to) { return ( diff --git a/packages/ui/src/containers/CreateAccount/index.tsx b/packages/ui/src/containers/CreateAccount/index.tsx index bc7310b1c..0e651f9b0 100644 --- a/packages/ui/src/containers/CreateAccount/index.tsx +++ b/packages/ui/src/containers/CreateAccount/index.tsx @@ -36,7 +36,7 @@ const defaultState: FieldState = { }; const CreateAccount = ({ className, autoFocus }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { termsValidation } = useTerms(); const { fieldValue, diff --git a/packages/ui/src/containers/PasscodeValidation/index.tsx b/packages/ui/src/containers/PasscodeValidation/index.tsx index f8330b797..2c43c40d4 100644 --- a/packages/ui/src/containers/PasscodeValidation/index.tsx +++ b/packages/ui/src/containers/PasscodeValidation/index.tsx @@ -34,7 +34,7 @@ const PasscodeValidation = ({ type, method, className, target }: Props) => { const [code, setCode] = useState([]); const [error, setError] = useState(); const { setToast } = useContext(PageContext); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { seconds, isRunning, restart } = useTimer({ autoStart: true, diff --git a/packages/ui/src/containers/Passwordless/EmailPasswordless.tsx b/packages/ui/src/containers/Passwordless/EmailPasswordless.tsx index dff47b21e..1eb2ee077 100644 --- a/packages/ui/src/containers/Passwordless/EmailPasswordless.tsx +++ b/packages/ui/src/containers/Passwordless/EmailPasswordless.tsx @@ -34,7 +34,7 @@ const defaultState: FieldState = { email: '' }; const EmailPasswordless = ({ type, autoFocus, className }: Props) => { const { setToast } = useContext(PageContext); const [showPasswordlessConfirmModal, setShowPasswordlessConfirmModal] = useState(false); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const navigate = useNavigate(); const { termsValidation } = useTerms(); const { fieldValue, setFieldValue, setFieldErrors, register, validateForm } = diff --git a/packages/ui/src/containers/Passwordless/PasswordlessConfirmModal.tsx b/packages/ui/src/containers/Passwordless/PasswordlessConfirmModal.tsx index 126cf0255..7c3d38e80 100644 --- a/packages/ui/src/containers/Passwordless/PasswordlessConfirmModal.tsx +++ b/packages/ui/src/containers/Passwordless/PasswordlessConfirmModal.tsx @@ -18,7 +18,7 @@ type Props = { }; const PasswordlessConfirmModal = ({ className, isOpen, type, method, value, onClose }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const sendPasscode = getSendPasscodeApi(type, method); const navigate = useNavigate(); const { isMobile } = usePlatform(); diff --git a/packages/ui/src/containers/Passwordless/PhonePasswordless.tsx b/packages/ui/src/containers/Passwordless/PhonePasswordless.tsx index b34321192..e9e84ec5e 100644 --- a/packages/ui/src/containers/Passwordless/PhonePasswordless.tsx +++ b/packages/ui/src/containers/Passwordless/PhonePasswordless.tsx @@ -34,7 +34,7 @@ const defaultState: FieldState = { phone: '' }; const PhonePasswordless = ({ type, autoFocus, className }: Props) => { const { setToast } = useContext(PageContext); const [showPasswordlessConfirmModal, setShowPasswordlessConfirmModal] = useState(false); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { phoneNumber, setPhoneNumber, isValidPhoneNumber } = usePhoneNumber(); const navigate = useNavigate(); const { termsValidation } = useTerms(); diff --git a/packages/ui/src/containers/SignInMethodsLink/index.tsx b/packages/ui/src/containers/SignInMethodsLink/index.tsx index a22ed9c10..f5c87b4d8 100644 --- a/packages/ui/src/containers/SignInMethodsLink/index.tsx +++ b/packages/ui/src/containers/SignInMethodsLink/index.tsx @@ -13,11 +13,11 @@ type Props = { signInMethods: LocalSignInMethod[]; search?: string; className?: string; - template?: TFuncKey<'translation', 'main_flow.secondary'>; + template?: TFuncKey<'translation', 'secondary'>; }; const SignInMethodsKeyMap: { - [key in LocalSignInMethod]: TFuncKey<'translation', 'main_flow.input'>; + [key in LocalSignInMethod]: TFuncKey<'translation', 'input'>; } = { username: 'username', email: 'email', @@ -26,7 +26,7 @@ const SignInMethodsKeyMap: { const SignInMethodsLink = ({ signInMethods, template, search, className }: Props) => { const navigate = useNavigate(); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const signInMethodsLink = useMemo( () => diff --git a/packages/ui/src/containers/SocialCreateAccount/index.tsx b/packages/ui/src/containers/SocialCreateAccount/index.tsx index c15cbc75d..7a6e70a16 100644 --- a/packages/ui/src/containers/SocialCreateAccount/index.tsx +++ b/packages/ui/src/containers/SocialCreateAccount/index.tsx @@ -16,7 +16,7 @@ type Props = { }; const SocialCreateAccount = ({ connectorId, className }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { relatedUser, localSignInMethods, registerWithSocial, bindSocialRelatedUser } = useBindSocial(); diff --git a/packages/ui/src/containers/SocialSignIn/SocialSignInDropdown/index.tsx b/packages/ui/src/containers/SocialSignIn/SocialSignInDropdown/index.tsx index 3de84a51d..7fbb809b5 100644 --- a/packages/ui/src/containers/SocialSignIn/SocialSignInDropdown/index.tsx +++ b/packages/ui/src/containers/SocialSignIn/SocialSignInDropdown/index.tsx @@ -1,4 +1,4 @@ -import { Language } from '@logto/phrases'; +import { Language } from '@logto/phrases-ui'; import React, { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/ui/src/containers/TermsOfUse/TermsOfUseConfirmModal/index.tsx b/packages/ui/src/containers/TermsOfUse/TermsOfUseConfirmModal/index.tsx index e305ce3bb..7844b26ae 100644 --- a/packages/ui/src/containers/TermsOfUse/TermsOfUseConfirmModal/index.tsx +++ b/packages/ui/src/containers/TermsOfUse/TermsOfUseConfirmModal/index.tsx @@ -17,7 +17,7 @@ type Props = { }; const TermsOfUseConfirmModal = ({ isOpen = false, termsUrl, onConfirm, onClose }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const terms = t('description.terms_of_use'); const content = t('description.agree_with_terms_modal', { terms }); diff --git a/packages/ui/src/containers/UsernameSignin/index.tsx b/packages/ui/src/containers/UsernameSignin/index.tsx index 61592c1ae..37e422b9d 100644 --- a/packages/ui/src/containers/UsernameSignin/index.tsx +++ b/packages/ui/src/containers/UsernameSignin/index.tsx @@ -33,7 +33,7 @@ const defaultState: FieldState = { }; const UsernameSignin = ({ className, autoFocus }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { termsValidation } = useTerms(); const { fieldValue, diff --git a/packages/ui/src/hooks/use-api.ts b/packages/ui/src/hooks/use-api.ts index 77792c42e..4bd54e651 100644 --- a/packages/ui/src/hooks/use-api.ts +++ b/packages/ui/src/hooks/use-api.ts @@ -1,4 +1,4 @@ -import { LogtoErrorCode } from '@logto/phrases'; +import type { LogtoErrorCode } from '@logto/phrases'; import { RequestErrorBody } from '@logto/schemas'; import { HTTPError } from 'ky'; import { useState, useCallback, useContext, useEffect } from 'react'; @@ -23,7 +23,7 @@ function useApi( api: (...args: Args) => Promise, errorHandlers?: ErrorHandlers ): UseApi { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const [error, setError] = useState(); const [result, setResult] = useState(); diff --git a/packages/ui/src/hooks/use-preview.ts b/packages/ui/src/hooks/use-preview.ts index 1208f12b7..86964254d 100644 --- a/packages/ui/src/hooks/use-preview.ts +++ b/packages/ui/src/hooks/use-preview.ts @@ -1,4 +1,4 @@ -import { Language } from '@logto/phrases'; +import { Language } from '@logto/phrases-ui'; import { AppearanceMode, ConnectorPlatform } from '@logto/schemas'; import { conditionalString } from '@silverhand/essentials'; import i18next from 'i18next'; diff --git a/packages/ui/src/hooks/use-social-landing-handler.ts b/packages/ui/src/hooks/use-social-landing-handler.ts index 46066b1d9..a9be45a0d 100644 --- a/packages/ui/src/hooks/use-social-landing-handler.ts +++ b/packages/ui/src/hooks/use-social-landing-handler.ts @@ -10,7 +10,7 @@ import { PageContext } from './use-page-context'; const useSocialLandingHandler = () => { const [loading, setLoading] = useState(true); const { setToast } = useContext(PageContext); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { search } = window.location; const socialLandingHandler = useCallback( diff --git a/packages/ui/src/hooks/use-social-sign-in-listener.ts b/packages/ui/src/hooks/use-social-sign-in-listener.ts index 0cd602a80..78733a4ea 100644 --- a/packages/ui/src/hooks/use-social-sign-in-listener.ts +++ b/packages/ui/src/hooks/use-social-sign-in-listener.ts @@ -11,7 +11,7 @@ import { PageContext } from './use-page-context'; const useSocialSignInListener = () => { const { setToast } = useContext(PageContext); - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const parameters = useParams(); const navigate = useNavigate(); diff --git a/packages/ui/src/i18n/init.ts b/packages/ui/src/i18n/init.ts index 9724372af..8e36cbe4a 100644 --- a/packages/ui/src/i18n/init.ts +++ b/packages/ui/src/i18n/init.ts @@ -1,4 +1,4 @@ -import resources from '@logto/phrases'; +import resources from '@logto/phrases-ui'; import { LanguageInfo } from '@logto/schemas'; import i18next, { InitOptions } from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; diff --git a/packages/ui/src/include.d/react-i18next.d.ts b/packages/ui/src/include.d/react-i18next.d.ts index adbe43039..816fe0207 100644 --- a/packages/ui/src/include.d/react-i18next.d.ts +++ b/packages/ui/src/include.d/react-i18next.d.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line import/no-unassigned-import import 'react-i18next'; -import en from '@logto/phrases/lib/locales/en.js'; +import en from '@logto/phrases-ui/lib/locales/en.js'; declare module 'react-i18next' { interface CustomTypeOptions { diff --git a/packages/ui/src/pages/ErrorPage/index.tsx b/packages/ui/src/pages/ErrorPage/index.tsx index 5ff673d01..c5c1be66a 100644 --- a/packages/ui/src/pages/ErrorPage/index.tsx +++ b/packages/ui/src/pages/ErrorPage/index.tsx @@ -9,13 +9,13 @@ import NavBar from '@/components/NavBar'; import * as styles from './index.module.scss'; type Props = { - title?: TFuncKey<'translation', 'main_flow'>; - message?: TFuncKey<'translation', 'main_flow'>; + title?: TFuncKey; + message?: TFuncKey; rawMessage?: string; }; const ErrorPage = ({ title = 'description.not_found', message, rawMessage }: Props) => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const navigate = useNavigate(); const errorMessage = rawMessage || (message && t(message)); diff --git a/packages/ui/src/pages/Passcode/index.tsx b/packages/ui/src/pages/Passcode/index.tsx index 83726ff3b..607c834c9 100644 --- a/packages/ui/src/pages/Passcode/index.tsx +++ b/packages/ui/src/pages/Passcode/index.tsx @@ -19,7 +19,7 @@ type Parameters = { type StateType = Nullable>; const Passcode = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { method, type } = useParams(); const state = useLocation().state as StateType; const invalidType = type !== 'sign-in' && type !== 'register'; diff --git a/packages/ui/src/pages/Register/index.tsx b/packages/ui/src/pages/Register/index.tsx index 041c9fbf5..2e4d3f8ac 100644 --- a/packages/ui/src/pages/Register/index.tsx +++ b/packages/ui/src/pages/Register/index.tsx @@ -14,7 +14,7 @@ type Parameters = { }; const Register = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { method = 'username' } = useParams(); const registerForm = useMemo(() => { diff --git a/packages/ui/src/pages/SecondarySignIn/index.tsx b/packages/ui/src/pages/SecondarySignIn/index.tsx index 6de706732..0a8760b63 100644 --- a/packages/ui/src/pages/SecondarySignIn/index.tsx +++ b/packages/ui/src/pages/SecondarySignIn/index.tsx @@ -14,7 +14,7 @@ type Props = { }; const SecondarySignIn = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { method = 'username' } = useParams(); const signInForm = useMemo(() => { diff --git a/packages/ui/src/pages/SocialRegister/index.tsx b/packages/ui/src/pages/SocialRegister/index.tsx index aed5914d8..a4cbb030d 100644 --- a/packages/ui/src/pages/SocialRegister/index.tsx +++ b/packages/ui/src/pages/SocialRegister/index.tsx @@ -13,7 +13,7 @@ type Parameters = { }; const SocialRegister = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' }); + const { t } = useTranslation(); const { connector } = useParams(); const { isMobile } = usePlatform(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 721ae6cb9..5f244f699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1066,10 +1066,30 @@ importers: prettier: 2.5.1 typescript: 4.6.2 + packages/phrases-ui: + specifiers: + '@silverhand/eslint-config': ^0.14.0 + '@silverhand/essentials': ^1.1.4 + '@silverhand/ts-config': ^0.14.0 + eslint: ^8.10.0 + lint-staged: ^13.0.0 + prettier: ^2.3.2 + typescript: ^4.6.2 + dependencies: + '@silverhand/essentials': 1.1.7 + devDependencies: + '@silverhand/eslint-config': 0.14.0_j52666lpucfyl6yew5a5mxpfce + '@silverhand/ts-config': 0.14.0_typescript@4.7.2 + eslint: 8.10.0 + lint-staged: 13.0.0 + prettier: 2.5.1 + typescript: 4.7.2 + packages/schemas: specifiers: '@logto/connector-types': ^0.1.0 '@logto/phrases': ^0.1.0 + '@logto/phrases-ui': ^0.1.0 '@logto/shared': ^0.1.0 '@silverhand/eslint-config': ^0.14.0 '@silverhand/essentials': ^1.1.6 @@ -1089,6 +1109,7 @@ importers: dependencies: '@logto/connector-types': link:../connector-types '@logto/phrases': link:../phrases + '@logto/phrases-ui': link:../phrases-ui '@logto/shared': link:../shared zod: 3.14.3 devDependencies: @@ -1138,6 +1159,7 @@ importers: packages/ui: specifiers: '@logto/phrases': ^0.1.0 + '@logto/phrases-ui': ^0.1.0 '@logto/schemas': ^0.1.0 '@parcel/core': 2.6.2 '@parcel/transformer-sass': 2.6.2 @@ -1188,6 +1210,7 @@ importers: use-debounced-loader: ^0.1.1 devDependencies: '@logto/phrases': link:../phrases + '@logto/phrases-ui': link:../phrases-ui '@logto/schemas': link:../schemas '@parcel/core': 2.6.2 '@parcel/transformer-sass': 2.6.2_@parcel+core@2.6.2 @@ -10637,10 +10660,10 @@ packages: dev: true /lodash.orderby/4.6.0: - resolution: {integrity: sha1-5pfwTOXXhSL1TZM4syuBozk+TrM=} + resolution: {integrity: sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==} /lodash.pick/4.4.0: - resolution: {integrity: sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=} + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} /lodash.set/4.3.2: resolution: {integrity: sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=}