@qlover/create-app 0.7.15 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
- package/dist/configs/_common/.github/workflows/release.yml +2 -2
- package/dist/configs/_common/.gitignore.template +6 -0
- package/dist/configs/_common/.prettierignore +17 -5
- package/dist/configs/_common/.vscode/settings.json +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +1 -1
- package/dist/templates/next-app/README.en.md +0 -1
- package/dist/templates/next-app/README.md +0 -1
- package/dist/templates/next-app/config/Identifier/api.ts +5 -5
- package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
- package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
- package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
- package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
- package/dist/templates/next-app/config/Identifier/index.ts +2 -12
- package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
- package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
- package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
- package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
- package/dist/templates/next-app/config/adminNavs.ts +19 -0
- package/dist/templates/next-app/config/common.ts +22 -13
- package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
- package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
- package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
- package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
- package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
- package/dist/templates/next-app/docs/en/index.md +0 -1
- package/dist/templates/next-app/docs/en/project-structure.md +0 -1
- package/dist/templates/next-app/docs/zh/index.md +0 -1
- package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
- package/dist/templates/next-app/make/generateLocales.ts +19 -12
- package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
- package/dist/templates/next-app/package.json +7 -3
- package/dist/templates/next-app/public/locales/en.json +172 -207
- package/dist/templates/next-app/public/locales/zh.json +172 -207
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
- package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
- package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
- package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
- package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
- package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
- package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
- package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
- package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
- package/dist/templates/next-app/src/i18n/request.ts +30 -1
- package/dist/templates/next-app/src/server/PageParams.ts +2 -10
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
- package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
- package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
- package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
- package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
- package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
- package/dist/templates/react-app/.prettierignore +17 -0
- package/dist/templates/react-app/README.en.md +71 -54
- package/dist/templates/react-app/README.md +35 -18
- package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
- package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
- package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
- package/dist/templates/react-app/config/IOCIdentifier.ts +11 -8
- package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
- package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
- package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
- package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
- package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
- package/dist/templates/react-app/config/Identifier/index.ts +1 -9
- package/dist/templates/react-app/config/Identifier/pages/index.ts +9 -0
- package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
- package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
- package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
- package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
- package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
- package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
- package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
- package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
- package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
- package/dist/templates/react-app/config/app.router.ts +81 -61
- package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
- package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
- package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
- package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
- package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
- package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
- package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
- package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
- package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
- package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
- package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
- package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
- package/dist/templates/react-app/config/theme.ts +14 -4
- package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/en/env.md +1132 -278
- package/dist/templates/react-app/docs/en/i18n.md +858 -147
- package/dist/templates/react-app/docs/en/index.md +733 -104
- package/dist/templates/react-app/docs/en/ioc.md +1228 -287
- package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/en/store.md +1194 -184
- package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/zh/env.md +1132 -275
- package/dist/templates/react-app/docs/zh/i18n.md +858 -147
- package/dist/templates/react-app/docs/zh/index.md +717 -104
- package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
- package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/zh/store.md +1192 -184
- package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
- package/dist/templates/react-app/e2e/App.spec.ts +319 -0
- package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
- package/dist/templates/react-app/e2e/main.spec.ts +20 -0
- package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
- package/dist/templates/react-app/eslint.config.mjs +247 -0
- package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
- package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
- package/dist/templates/react-app/package.json +31 -3
- package/dist/templates/react-app/playwright.config.ts +79 -0
- package/dist/templates/react-app/public/locales/en/common.json +233 -179
- package/dist/templates/react-app/public/locales/zh/common.json +233 -179
- package/dist/templates/react-app/src/App.tsx +15 -42
- package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
- package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
- package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
- package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
- package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
- package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
- package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
- package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
- package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
- package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
- package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
- package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
- package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
- package/dist/templates/react-app/src/base/types/Page.ts +14 -2
- package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
- package/dist/templates/react-app/src/core/IOC.ts +5 -46
- package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
- package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
- package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
- package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
- package/dist/templates/react-app/src/core/globals.ts +9 -9
- package/dist/templates/react-app/src/main.tsx +4 -4
- package/dist/templates/react-app/src/pages/404.tsx +6 -3
- package/dist/templates/react-app/src/pages/500.tsx +5 -2
- package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
- package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
- package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
- package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
- package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
- package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
- package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
- package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
- package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
- package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
- package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/react-app/src/styles/css/page.css +4 -3
- package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
- package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
- package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
- package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
- package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
- package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
- package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
- package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
- package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
- package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
- package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
- package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
- package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
- package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
- package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
- package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
- package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
- package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
- package/dist/templates/react-app/tsconfig.e2e.json +21 -0
- package/dist/templates/react-app/tsconfig.json +8 -1
- package/dist/templates/react-app/tsconfig.node.json +1 -1
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/dist/templates/react-app/vite.config.ts +50 -34
- package/package.json +2 -1
- package/dist/configs/react-app/eslint.config.js +0 -94
- package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
- package/dist/templates/next-app/config/Identifier/common.ts +0 -69
- package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
- package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
- package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
- package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
- package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
- package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
- package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
- package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
- package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/react-app/config/i18n.ts +0 -15
- package/dist/templates/react-app/docs/en/project-structure.md +0 -434
- package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
- package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
- package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
|
@@ -1,152 +1,568 @@
|
|
|
1
|
-
# Internationalization
|
|
1
|
+
# Internationalization (i18n)
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 📋 Table of Contents
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [Core Philosophy](#-core-philosophy) - Never hard-code, use variables only
|
|
6
|
+
- [What is an i18n Key](#-what-is-an-i18n-key) - Unique identifier
|
|
7
|
+
- [Why Use i18n Keys](#-why-use-i18n-keys) - Reduce cognitive burden
|
|
8
|
+
- [Implementation in the Project](#-implementation-in-the-project) - Bootstrap plugin
|
|
9
|
+
- [How to Use](#-how-to-use) - Practical guide
|
|
10
|
+
- [Best Practices](#-best-practices) - 7 core practices
|
|
11
|
+
- [FAQ](#-faq) - Common questions
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
- **TypeScript Comments**: Automatically generate translation resources through comments
|
|
9
|
-
- **Type Safety**: Complete type checking and auto-completion
|
|
10
|
-
- **Router Integration**: Support language parameters in URL paths
|
|
11
|
-
- **Auto Generation**: Automatically generate translation files from source code
|
|
12
|
-
- **Developer Friendly**: Complete development tool support
|
|
13
|
+
---
|
|
13
14
|
|
|
14
|
-
## Core
|
|
15
|
+
## 🎯 Core Philosophy
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
> **🚨 Important Principle: All text, words, and sentences that need to be displayed in the project must use i18n Keys. Hard-coded text is absolutely not allowed!**
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
> **⭐ Core Advantage: Developers don't need to remember complex translation strings, just variable names. The IDE will provide auto-completion!**
|
|
20
|
+
|
|
21
|
+
### Core Concept
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
❌ Hard-coded text ✅ i18n Key (variable)
|
|
25
|
+
"Login" → BUTTON_LOGIN (no need to remember 'common:button.login')
|
|
26
|
+
"Welcome back" → MESSAGE_WELCOME (no need to remember 'common:message.welcome')
|
|
27
|
+
"Are you sure?" → CONFIRM_DELETE (no need to remember 'common:confirm.delete')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Every text has a unique identifier:**
|
|
19
31
|
|
|
20
32
|
```typescript
|
|
21
|
-
//
|
|
33
|
+
// ❌ Wrong: Write text directly or use strings
|
|
34
|
+
<button>Login</button>
|
|
35
|
+
<h1>Welcome back</h1>
|
|
36
|
+
<p>{t('common:confirm.delete')}</p> // Don't write strings either
|
|
37
|
+
|
|
38
|
+
// ✅ Correct: Use i18n Key variables
|
|
39
|
+
<button>{t(BUTTON_LOGIN)}</button> // Just remember the variable name
|
|
40
|
+
<h1>{t(MESSAGE_WELCOME)}</h1> // IDE will auto-complete
|
|
41
|
+
<p>{t(CONFIRM_DELETE)}</p> // TypeScript will check spelling
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔑 What is an i18n Key
|
|
47
|
+
|
|
48
|
+
An i18n Key is **a unique identifier for each text**, just like everyone has an ID number.
|
|
49
|
+
|
|
50
|
+
### Basic Concept
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// i18n Key definition (in config/Identifier/ directory)
|
|
22
54
|
|
|
23
55
|
/**
|
|
24
|
-
* @description
|
|
25
|
-
* @localZh
|
|
26
|
-
* @localEn
|
|
56
|
+
* @description Login button text
|
|
57
|
+
* @localZh 登录
|
|
58
|
+
* @localEn Login
|
|
27
59
|
*/
|
|
28
|
-
export const
|
|
60
|
+
export const BUTTON_LOGIN = 'common:button.login';
|
|
61
|
+
// ↑ Constant name ↑ Identifier string
|
|
62
|
+
// Used in code Key name stored in translation files
|
|
29
63
|
|
|
30
64
|
/**
|
|
31
|
-
* @description
|
|
32
|
-
* @localZh
|
|
33
|
-
* @localEn
|
|
65
|
+
* @description Welcome message
|
|
66
|
+
* @localZh 欢迎回来,{{name}}!
|
|
67
|
+
* @localEn Welcome back, {{name}}!
|
|
34
68
|
*/
|
|
35
|
-
export const
|
|
69
|
+
export const MESSAGE_WELCOME = 'common:message.welcome';
|
|
36
70
|
```
|
|
37
71
|
|
|
38
|
-
|
|
72
|
+
**Structure explanation:**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
'common:button.login'
|
|
76
|
+
↑ ↑ ↑
|
|
77
|
+
Namespace Category Specific identifier
|
|
78
|
+
```
|
|
39
79
|
|
|
40
|
-
|
|
80
|
+
### Naming Conventions
|
|
41
81
|
|
|
42
82
|
```typescript
|
|
43
|
-
|
|
83
|
+
// ✅ Good naming: Clear and structured
|
|
84
|
+
export const BUTTON_LOGIN = 'common:button.login'; // Button text
|
|
85
|
+
export const BUTTON_SUBMIT = 'common:button.submit'; // Button text
|
|
86
|
+
export const MESSAGE_WELCOME = 'common:message.welcome'; // Message text
|
|
87
|
+
export const CONFIRM_DELETE = 'common:confirm.delete'; // Confirm dialog
|
|
88
|
+
export const ERROR_NETWORK = 'common:error.network'; // Error message
|
|
89
|
+
export const PAGE_HOME_TITLE = 'common:page.home.title'; // Page title
|
|
90
|
+
|
|
91
|
+
// ❌ Bad naming: Vague and unstructured
|
|
92
|
+
export const TEXT1 = 'login';
|
|
93
|
+
export const MSG = 'welcome';
|
|
94
|
+
export const DELETE_CONFIRM = 'delete_confirm';
|
|
95
|
+
```
|
|
44
96
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🤔 Why Use i18n Keys
|
|
100
|
+
|
|
101
|
+
### Problem: Pain Points of Hard-coded Text
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ❌ Traditional approach: Hard-coded text
|
|
105
|
+
|
|
106
|
+
function LoginPage() {
|
|
107
|
+
return (
|
|
108
|
+
<div>
|
|
109
|
+
<h1>Login</h1>
|
|
110
|
+
<button>Login</button>
|
|
111
|
+
<p>Welcome back!</p>
|
|
112
|
+
<span>Forgot password?</span>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 😰 Problem 1: Difficult to internationalize
|
|
118
|
+
// If we need to support Chinese, need to modify lots of code
|
|
119
|
+
|
|
120
|
+
// 😰 Problem 2: Hard to maintain
|
|
121
|
+
// Same text might be used in multiple places, easy to miss when changing
|
|
122
|
+
|
|
123
|
+
// 😰 Problem 3: Hard to manage uniformly
|
|
124
|
+
// Can't count how many texts exist in the project, which need translation
|
|
125
|
+
|
|
126
|
+
// 😰 Problem 4: Hard to search
|
|
127
|
+
// To find where a text is used, can only globally search strings
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Solution: Use i18n Keys
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// ✅ Use i18n Keys
|
|
134
|
+
|
|
135
|
+
import { useAppTranslation } from '@/uikit/hooks/useAppTranslation';
|
|
136
|
+
import {
|
|
137
|
+
PAGE_LOGIN_TITLE,
|
|
138
|
+
BUTTON_LOGIN,
|
|
139
|
+
MESSAGE_WELCOME,
|
|
140
|
+
LINK_FORGOT_PASSWORD
|
|
141
|
+
} from '@config/Identifier';
|
|
142
|
+
|
|
143
|
+
function LoginPage() {
|
|
144
|
+
const { t } = useAppTranslation();
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div>
|
|
148
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
149
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
150
|
+
<p>{t(MESSAGE_WELCOME)}</p>
|
|
151
|
+
<span>{t(LINK_FORGOT_PASSWORD)}</span>
|
|
152
|
+
</div>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ✅ Advantage 1: Automatic internationalization
|
|
157
|
+
// Automatically displays corresponding text based on user language
|
|
158
|
+
|
|
159
|
+
// ✅ Advantage 2: Centralized management
|
|
160
|
+
// All text managed uniformly in Identifier files
|
|
161
|
+
|
|
162
|
+
// ✅ Advantage 3: Easy to maintain
|
|
163
|
+
// Only need to modify in one place
|
|
164
|
+
|
|
165
|
+
// ✅ Advantage 4: Type safe
|
|
166
|
+
// TypeScript will check if Key exists
|
|
167
|
+
|
|
168
|
+
// ✅ Advantage 5: Reduce thinking ⭐
|
|
169
|
+
// Developers only need to know BUTTON_LOGIN is a variable
|
|
170
|
+
// Don't need to remember 'common:button.login' string
|
|
171
|
+
// IDE will provide auto-completion, wrong spelling will immediately error
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Comparison Summary
|
|
175
|
+
|
|
176
|
+
| Feature | Hard-coded Text | i18n Key |
|
|
177
|
+
| ------------------------ | ---------------------------------- | ------------------------------------ |
|
|
178
|
+
| **Internationalization** | ❌ Difficult (need to change code) | ✅ Automatic (just switch language) |
|
|
179
|
+
| **Maintainability** | ❌ Poor (scattered everywhere) | ✅ Good (centralized management) |
|
|
180
|
+
| **Reusability** | ❌ Poor (copy-paste) | ✅ Good (reference Key) |
|
|
181
|
+
| **Searchability** | ❌ Difficult (string search) | ✅ Simple (search constant) |
|
|
182
|
+
| **Countability** | ❌ Impossible | ✅ Easy (count Keys) |
|
|
183
|
+
| **Type Safety** | ❌ None | ✅ Yes (TypeScript) |
|
|
184
|
+
| **Cognitive Burden** | ❌ High (need to remember text) | ✅ Low (just remember variable name) |
|
|
185
|
+
| **IDE Support** | ❌ No auto-complete | ✅ Full auto-complete |
|
|
186
|
+
|
|
187
|
+
### ⭐ Core Advantage: Reduce Developer Thinking
|
|
188
|
+
|
|
189
|
+
**The most important advantage: Developers don't need to care about what the actual i18n string is!**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// ❌ Traditional approach: Need to remember complex strings
|
|
193
|
+
function LoginPage() {
|
|
194
|
+
const { t } = useTranslation();
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
{/* 😰 Need to remember 'common:button.login' */}
|
|
199
|
+
<button>{t('common:button.login')}</button>
|
|
200
|
+
|
|
201
|
+
{/* 😰 Need to remember 'page.login.title' */}
|
|
202
|
+
<h1>{t('page.login.title')}</h1>
|
|
203
|
+
|
|
204
|
+
{/* 😰 Spelling mistake won't error */}
|
|
205
|
+
<p>{t('page.login.welcom')}</p> {/* welcom → welcome, spelling error */}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ✅ i18n Key approach: Just need to know variable name
|
|
211
|
+
import { BUTTON_LOGIN, PAGE_LOGIN_TITLE, PAGE_LOGIN_WELCOME } from '@config/Identifier';
|
|
212
|
+
|
|
213
|
+
function LoginPage() {
|
|
214
|
+
const { t } = useAppTranslation();
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div>
|
|
218
|
+
{/* ✅ Just remember BUTTON_LOGIN, IDE will auto-complete */}
|
|
219
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
220
|
+
|
|
221
|
+
{/* ✅ Type PAGE_ and IDE will show all available Keys */}
|
|
222
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
223
|
+
|
|
224
|
+
{/* ✅ Wrong spelling will immediately error in TypeScript */}
|
|
225
|
+
<p>{t(PAGE_LOGIN_WELCOM)}</p> {/* ❌ TS error: Variable not found */}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Development Experience Comparison:**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// ❌ Using strings workflow:
|
|
235
|
+
// 1. 😰 Check translation files, find corresponding key
|
|
236
|
+
// 2. 😰 Remember complete key path (like 'common:page.login.title')
|
|
237
|
+
// 3. 😰 Manually type the string in code
|
|
238
|
+
// 4. 😰 May type incorrectly, but compiler won't error
|
|
239
|
+
// 5. 😰 Only discover translation didn't work at runtime
|
|
240
|
+
|
|
241
|
+
// ✅ Using i18n Key workflow:
|
|
242
|
+
// 1. 😊 Type variable name prefix (like BUTTON_)
|
|
243
|
+
// 2. 😊 IDE automatically suggests all available Keys
|
|
244
|
+
// 3. 😊 Select needed Key, IDE auto-completes
|
|
245
|
+
// 4. 😊 If typed incorrectly, TypeScript immediately errors
|
|
246
|
+
// 5. 😊 Can ensure Key correctness at compile time
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Cognitive Burden Comparison:**
|
|
250
|
+
|
|
251
|
+
| What Developers Need to Remember | String Method | i18n Key Method |
|
|
252
|
+
| -------------------------------- | --------------------------------------------- | ------------------------------ |
|
|
253
|
+
| **Translation File Structure** | ✅ Must remember (like `common:button.login`) | ❌ Don't need to |
|
|
254
|
+
| **Namespace** | ✅ Must remember (like `common:`, `page:`) | ❌ Don't need to |
|
|
255
|
+
| **String Path** | ✅ Must remember complete path | ❌ Don't need to |
|
|
256
|
+
| **Variable Name** | ❌ No variable | ✅ Just remember variable name |
|
|
257
|
+
| **IDE Hints** | ❌ None | ✅ Full hints |
|
|
258
|
+
|
|
259
|
+
**Real Development Scenarios:**
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Scenario 1: Write a new button
|
|
263
|
+
|
|
264
|
+
// ❌ String method (need to think a lot)
|
|
265
|
+
// 1. What should this button's translation key be called?
|
|
266
|
+
// 2. Which namespace should it go in? common? page?
|
|
267
|
+
// 3. What should the path be? button.submit? submit.button?
|
|
268
|
+
// 4. Finally write: t('common:button.submit')
|
|
269
|
+
<button>{t('common:button.submit')}</button>
|
|
270
|
+
|
|
271
|
+
// ✅ i18n Key method (just need to search)
|
|
272
|
+
// 1. Type BUTTON_
|
|
273
|
+
// 2. IDE shows all available button Keys
|
|
274
|
+
// 3. See BUTTON_SUBMIT, select it
|
|
275
|
+
// 4. Done!
|
|
276
|
+
<button>{t(BUTTON_SUBMIT)}</button>
|
|
277
|
+
|
|
278
|
+
// Scenario 2: Change text on another page
|
|
279
|
+
|
|
280
|
+
// ❌ String method
|
|
281
|
+
// 1. Check that page's code
|
|
282
|
+
// 2. Find t('page.home.welcome')
|
|
283
|
+
// 3. Remember this string
|
|
284
|
+
// 4. Search and modify in translation files
|
|
285
|
+
|
|
286
|
+
// ✅ i18n Key method
|
|
287
|
+
// 1. See PAGE_HOME_WELCOME in code
|
|
288
|
+
// 2. Jump directly to definition (IDE's Go to Definition)
|
|
289
|
+
// 3. Modify translation in comment
|
|
290
|
+
// 4. Automatically generate new translation files
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Summary:**
|
|
294
|
+
|
|
295
|
+
Using i18n Keys allows developers to:
|
|
296
|
+
|
|
297
|
+
- ✅ **Don't need to remember** complex string paths
|
|
298
|
+
- ✅ **Don't need to remember** translation file structure
|
|
299
|
+
- ✅ **Don't need to care about** specific translation key names
|
|
300
|
+
- ✅ **Just need to know** this is a variable
|
|
301
|
+
- ✅ **Rely on IDE** provided auto-complete and type checking
|
|
302
|
+
- ✅ **Focus on** business logic, not translation details
|
|
303
|
+
|
|
304
|
+
> 💡 **Core Philosophy: Let developers put their energy into business logic, not memorizing translation strings!**
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 🛠️ Implementation in the Project
|
|
309
|
+
|
|
310
|
+
### 1. File Structure
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
config/
|
|
314
|
+
└── Identifier/ # i18n Key definition directory
|
|
315
|
+
├── index.ts # Export all Keys
|
|
316
|
+
├── common/ # Common text
|
|
317
|
+
│ ├── index.ts
|
|
318
|
+
│ ├── common.ts # General text (buttons, messages, etc.)
|
|
319
|
+
│ └── common.error.ts # Error messages
|
|
320
|
+
└── pages/ # Page text
|
|
321
|
+
├── index.ts
|
|
322
|
+
├── page.home.ts # Home page text
|
|
323
|
+
├── page.login.ts # Login page text
|
|
324
|
+
└── page.about.ts # About page text
|
|
325
|
+
|
|
326
|
+
public/
|
|
327
|
+
└── locales/ # Generated translation files
|
|
328
|
+
├── zh/
|
|
329
|
+
│ └── common.json # Chinese translations
|
|
330
|
+
└── en/
|
|
331
|
+
└── common.json # English translations
|
|
332
|
+
|
|
333
|
+
src/
|
|
334
|
+
└── uikit/
|
|
335
|
+
└── hooks/
|
|
336
|
+
└── useAppTranslation.ts # Translation Hook
|
|
62
337
|
```
|
|
63
338
|
|
|
64
|
-
###
|
|
339
|
+
### 2. i18n Configuration
|
|
65
340
|
|
|
66
341
|
```typescript
|
|
67
|
-
// config/i18n.ts
|
|
68
|
-
export
|
|
69
|
-
fallbackLng: 'en',
|
|
342
|
+
// config/i18n/i18nConfig.ts
|
|
343
|
+
export const i18nConfig = {
|
|
344
|
+
fallbackLng: 'en', // Default language
|
|
70
345
|
debug: false,
|
|
71
346
|
interpolation: {
|
|
72
347
|
escapeValue: false
|
|
73
348
|
},
|
|
74
|
-
ns: ['common'],
|
|
349
|
+
ns: ['common'], // Namespaces
|
|
75
350
|
defaultNS: 'common',
|
|
76
351
|
backend: {
|
|
77
|
-
loadPath: '/locales/{{lng}}/{{ns}}.json'
|
|
352
|
+
loadPath: '/locales/{{lng}}/{{ns}}.json' // Translation file path
|
|
78
353
|
},
|
|
79
|
-
supportedLngs: ['en', 'zh']
|
|
354
|
+
supportedLngs: ['en', 'zh'] // Supported languages
|
|
80
355
|
} as const;
|
|
81
356
|
```
|
|
82
357
|
|
|
83
|
-
|
|
358
|
+
### 3. I18nService (Bootstrap Plugin)
|
|
84
359
|
|
|
85
|
-
|
|
360
|
+
```typescript
|
|
361
|
+
// src/base/services/I18nService.ts
|
|
362
|
+
export class I18nService implements BootstrapExecutorPlugin {
|
|
363
|
+
readonly pluginName = 'I18nService';
|
|
364
|
+
|
|
365
|
+
constructor(protected pathname: string) {
|
|
366
|
+
super(() => new I18nServiceState(i18n.language));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Initialize i18n on Bootstrap startup
|
|
371
|
+
*/
|
|
372
|
+
onBefore(): void {
|
|
373
|
+
i18n
|
|
374
|
+
.use(HttpApi) // Load translation files
|
|
375
|
+
.use(LanguageDetector) // Language detection
|
|
376
|
+
.use(initReactI18next) // React integration
|
|
377
|
+
.init(i18nConfig); // Initialize config
|
|
378
|
+
|
|
379
|
+
// Add custom language detector (detect from URL path)
|
|
380
|
+
const pathLanguageDetector = {
|
|
381
|
+
name: 'pathLanguageDetector',
|
|
382
|
+
lookup: () => {
|
|
383
|
+
const paths = this.pathname.split('/');
|
|
384
|
+
for (const path of paths) {
|
|
385
|
+
if (this.isValidLanguage(path)) {
|
|
386
|
+
return path; // Extract language from URL (like /zh/home)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return fallbackLng;
|
|
390
|
+
},
|
|
391
|
+
cacheUserLanguage(lng: string) {
|
|
392
|
+
localStorage.setItem('i18nextLng', lng);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
i18n.services.languageDetector.addDetector(pathLanguageDetector);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Switch language
|
|
401
|
+
*/
|
|
402
|
+
async changeLanguage(language: string): Promise<void> {
|
|
403
|
+
await i18n.changeLanguage(language);
|
|
404
|
+
localStorage.setItem('i18nextLng', language);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Translate text
|
|
409
|
+
*/
|
|
410
|
+
t(key: string, params?: Record<string, unknown>): string {
|
|
411
|
+
return i18n.t(key, { lng: i18n.language, ...params });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### 4. Auto-generate Translation Files
|
|
86
417
|
|
|
87
418
|
```typescript
|
|
88
|
-
// config
|
|
419
|
+
// vite.config.ts
|
|
420
|
+
import ts2Locales from '@brain-toolkit/ts2locales/vite';
|
|
421
|
+
|
|
422
|
+
export default defineConfig({
|
|
423
|
+
plugins: [
|
|
424
|
+
// ✅ ts2locales plugin: Auto-generate translation files from TypeScript comments
|
|
425
|
+
ts2Locales({
|
|
426
|
+
locales: ['en', 'zh'],
|
|
427
|
+
options: generateTs2LocalesOptions() // Configure generation rules
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**How it works:**
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// 1. Define Key in Identifier file
|
|
437
|
+
/**
|
|
438
|
+
* @description Login button text
|
|
439
|
+
* @localZh 登录
|
|
440
|
+
* @localEn Login
|
|
441
|
+
*/
|
|
442
|
+
export const BUTTON_LOGIN = 'common:button.login';
|
|
443
|
+
|
|
444
|
+
// 2. ts2locales plugin automatically generates translation files
|
|
445
|
+
|
|
446
|
+
// public/locales/zh/common.json
|
|
447
|
+
{
|
|
448
|
+
"button": {
|
|
449
|
+
"login": "登录"
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// public/locales/en/common.json
|
|
454
|
+
{
|
|
455
|
+
"button": {
|
|
456
|
+
"login": "Login"
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## 📝 How to Use
|
|
464
|
+
|
|
465
|
+
### 1. Define i18n Key
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
// config/Identifier/pages/page.home.ts
|
|
89
469
|
|
|
90
470
|
/**
|
|
91
471
|
* @description Home page title
|
|
92
472
|
* @localZh 首页
|
|
93
473
|
* @localEn Home
|
|
94
474
|
*/
|
|
95
|
-
export const PAGE_HOME_TITLE = 'page.home.title';
|
|
475
|
+
export const PAGE_HOME_TITLE = 'common:page.home.title';
|
|
96
476
|
|
|
97
477
|
/**
|
|
98
478
|
* @description Home page welcome message
|
|
99
|
-
* @localZh
|
|
100
|
-
* @localEn Welcome to our application
|
|
479
|
+
* @localZh 欢迎来到我们的应用!
|
|
480
|
+
* @localEn Welcome to our application!
|
|
101
481
|
*/
|
|
102
|
-
export const PAGE_HOME_WELCOME = 'page.home.welcome';
|
|
482
|
+
export const PAGE_HOME_WELCOME = 'common:page.home.welcome';
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* @description Home page description with user name
|
|
486
|
+
* @localZh 你好,{{name}}!今天是个好日子。
|
|
487
|
+
* @localEn Hello, {{name}}! Have a great day.
|
|
488
|
+
*/
|
|
489
|
+
export const PAGE_HOME_GREETING = 'common:page.home.greeting';
|
|
103
490
|
```
|
|
104
491
|
|
|
105
|
-
|
|
492
|
+
**Comment conventions:**
|
|
493
|
+
|
|
494
|
+
- `@description`: Explain text purpose (English)
|
|
495
|
+
- `@localZh`: Chinese translation
|
|
496
|
+
- `@localEn`: English translation
|
|
106
497
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
498
|
+
### 2. Use in UI Components
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// src/pages/base/HomePage.tsx
|
|
502
|
+
import { useAppTranslation } from '@/uikit/hooks/useAppTranslation';
|
|
503
|
+
import {
|
|
504
|
+
PAGE_HOME_TITLE,
|
|
505
|
+
PAGE_HOME_WELCOME,
|
|
506
|
+
PAGE_HOME_GREETING
|
|
507
|
+
} from '@config/Identifier';
|
|
110
508
|
|
|
111
509
|
function HomePage() {
|
|
112
|
-
const { t } =
|
|
510
|
+
const { t } = useAppTranslation();
|
|
511
|
+
const userName = 'John';
|
|
113
512
|
|
|
114
513
|
return (
|
|
115
514
|
<div>
|
|
116
|
-
|
|
117
|
-
<
|
|
515
|
+
{/* ✅ Simple text */}
|
|
516
|
+
<h1>{t(PAGE_HOME_TITLE)}</h1>
|
|
517
|
+
<p>{t(PAGE_HOME_WELCOME)}</p>
|
|
518
|
+
|
|
519
|
+
{/* ✅ Text with parameters */}
|
|
520
|
+
<p>{t(PAGE_HOME_GREETING, { name: userName })}</p>
|
|
118
521
|
</div>
|
|
119
522
|
);
|
|
120
523
|
}
|
|
121
524
|
```
|
|
122
525
|
|
|
123
|
-
### 3.
|
|
526
|
+
### 3. Use in Services
|
|
124
527
|
|
|
125
528
|
```typescript
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
529
|
+
// src/base/services/UserService.ts
|
|
530
|
+
import { injectable, inject } from 'inversify';
|
|
531
|
+
import { I18nServiceInterface } from '@/base/port/I18nServiceInterface';
|
|
532
|
+
import { ERROR_USER_NOT_FOUND } from '@config/Identifier';
|
|
533
|
+
|
|
534
|
+
@injectable()
|
|
535
|
+
export class UserService {
|
|
536
|
+
constructor(
|
|
537
|
+
@inject(IOCIdentifier.I18nServiceInterface)
|
|
538
|
+
private i18n: I18nServiceInterface
|
|
539
|
+
) {}
|
|
540
|
+
|
|
541
|
+
async getUser(id: string) {
|
|
542
|
+
const user = await this.api.getUserById(id);
|
|
543
|
+
|
|
544
|
+
if (!user) {
|
|
545
|
+
// ✅ Use i18n in service
|
|
546
|
+
throw new Error(this.i18n.t(ERROR_USER_NOT_FOUND, { id }));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return user;
|
|
550
|
+
}
|
|
136
551
|
}
|
|
137
552
|
```
|
|
138
553
|
|
|
139
|
-
### 4. Language
|
|
554
|
+
### 4. Switch Language
|
|
140
555
|
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
import {
|
|
556
|
+
```typescript
|
|
557
|
+
// src/uikit/components/LanguageSwitcher.tsx
|
|
558
|
+
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
144
559
|
|
|
145
560
|
function LanguageSwitcher() {
|
|
146
|
-
const i18nService =
|
|
561
|
+
const i18nService = useIOC('I18nServiceInterface');
|
|
147
562
|
const currentLang = i18nService.getCurrentLanguage();
|
|
148
563
|
|
|
149
|
-
const handleChange = (lang:
|
|
564
|
+
const handleChange = (lang: 'zh' | 'en') => {
|
|
565
|
+
// ✅ Switch language
|
|
150
566
|
i18nService.changeLanguage(lang);
|
|
151
567
|
};
|
|
152
568
|
|
|
@@ -159,110 +575,405 @@ function LanguageSwitcher() {
|
|
|
159
575
|
}
|
|
160
576
|
```
|
|
161
577
|
|
|
162
|
-
|
|
578
|
+
### 5. Complex Scenarios
|
|
163
579
|
|
|
164
|
-
|
|
580
|
+
#### Scenario 1: Dynamic Text
|
|
165
581
|
|
|
166
|
-
|
|
582
|
+
```typescript
|
|
583
|
+
/**
|
|
584
|
+
* @description Upload progress message
|
|
585
|
+
* @localZh 已上传 {{current}} / {{total}} 个文件
|
|
586
|
+
* @localEn Uploaded {{current}} / {{total}} files
|
|
587
|
+
*/
|
|
588
|
+
export const UPLOAD_PROGRESS = 'common:upload.progress';
|
|
167
589
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
590
|
+
// Usage
|
|
591
|
+
<p>{t(UPLOAD_PROGRESS, { current: 3, total: 10 })}</p>
|
|
592
|
+
// Chinese: 已上传 3 / 10 个文件
|
|
593
|
+
// English: Uploaded 3 / 10 files
|
|
594
|
+
```
|
|
172
595
|
|
|
173
|
-
|
|
174
|
-
AUTH_LOGIN_BUTTON;
|
|
175
|
-
AUTH_LOGOUT_CONFIRM;
|
|
596
|
+
#### Scenario 2: Plural Forms
|
|
176
597
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
598
|
+
```typescript
|
|
599
|
+
/**
|
|
600
|
+
* @description Files count message
|
|
601
|
+
* @localZh {{count}} 个文件
|
|
602
|
+
* @localEn {{count}} file_plural
|
|
603
|
+
*/
|
|
604
|
+
export const FILES_COUNT = 'common:files.count';
|
|
181
605
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
│ ├── common.ts // Common text
|
|
187
|
-
│ ├── page.ts // Page text
|
|
188
|
-
│ ├── error.ts // Error messages
|
|
189
|
-
│ └── auth.ts // Authentication related
|
|
190
|
-
```
|
|
606
|
+
// Usage
|
|
607
|
+
<p>{t(FILES_COUNT, { count: 1 })}</p> // 1 file
|
|
608
|
+
<p>{t(FILES_COUNT, { count: 5 })}</p> // 5 files
|
|
609
|
+
```
|
|
191
610
|
|
|
192
|
-
|
|
611
|
+
#### Scenario 3: HTML Content
|
|
193
612
|
|
|
194
613
|
```typescript
|
|
195
614
|
/**
|
|
196
|
-
* @description
|
|
197
|
-
* @localZh
|
|
198
|
-
* @localEn
|
|
615
|
+
* @description Terms of service agreement
|
|
616
|
+
* @localZh 我已阅读并同意<a href="/terms">服务条款</a>
|
|
617
|
+
* @localEn I have read and agree to the <a href="/terms">Terms of Service</a>
|
|
199
618
|
*/
|
|
619
|
+
export const TERMS_AGREEMENT = 'common:terms.agreement';
|
|
620
|
+
|
|
621
|
+
// Usage (Need to use Trans component in React)
|
|
622
|
+
import { Trans } from 'react-i18next';
|
|
623
|
+
|
|
624
|
+
<Trans i18nKey={TERMS_AGREEMENT}>
|
|
625
|
+
I have read and agree to the <a href="/terms">Terms of Service</a>
|
|
626
|
+
</Trans>
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## 💎 Best Practices
|
|
632
|
+
|
|
633
|
+
### 1. ✅ All Text Uses i18n Keys
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
// ❌ Wrong: Mix hard-coded and i18n Keys
|
|
637
|
+
function LoginForm() {
|
|
638
|
+
return (
|
|
639
|
+
<form>
|
|
640
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
641
|
+
<button>Login</button> {/* ❌ Hard-coded */}
|
|
642
|
+
<a href="/forgot">Forgot password?</a> {/* ❌ Hard-coded */}
|
|
643
|
+
</form>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ✅ Correct: All text uses i18n Keys
|
|
648
|
+
function LoginForm() {
|
|
649
|
+
return (
|
|
650
|
+
<form>
|
|
651
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
652
|
+
<button>{t(BUTTON_LOGIN)}</button> {/* ✅ Use Key */}
|
|
653
|
+
<a href="/forgot">{t(LINK_FORGOT_PASSWORD)}</a> {/* ✅ Use Key */}
|
|
654
|
+
</form>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
200
657
|
```
|
|
201
658
|
|
|
202
|
-
###
|
|
659
|
+
### 2. ✅ Organize i18n Keys Reasonably
|
|
203
660
|
|
|
204
661
|
```typescript
|
|
205
|
-
//
|
|
206
|
-
|
|
662
|
+
// ✅ Good organization: Categorize by functional modules
|
|
663
|
+
config/Identifier/
|
|
664
|
+
├── common/
|
|
665
|
+
│ ├── common.ts # General text (buttons, labels, etc.)
|
|
666
|
+
│ └── common.error.ts # Error messages
|
|
667
|
+
├── pages/
|
|
668
|
+
│ ├── page.home.ts # Home page text
|
|
669
|
+
│ ├── page.login.ts # Login page text
|
|
670
|
+
│ └── page.about.ts # About page text
|
|
671
|
+
└── components/
|
|
672
|
+
├── component.header.ts # Header component text
|
|
673
|
+
└── component.footer.ts # Footer component text
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### 3. ✅ Use Semantic Naming
|
|
207
677
|
|
|
208
|
-
|
|
209
|
-
|
|
678
|
+
```typescript
|
|
679
|
+
// ✅ Good naming: Clearly expresses meaning
|
|
680
|
+
export const BUTTON_SUBMIT = 'common:button.submit';
|
|
681
|
+
export const BUTTON_CANCEL = 'common:button.cancel';
|
|
682
|
+
export const ERROR_NETWORK = 'common:error.network';
|
|
683
|
+
export const MESSAGE_SUCCESS = 'common:message.success';
|
|
684
|
+
|
|
685
|
+
// ❌ Bad naming: Unclear meaning
|
|
686
|
+
export const BTN1 = 'btn1';
|
|
687
|
+
export const TEXT = 'text';
|
|
688
|
+
export const MSG = 'msg';
|
|
210
689
|
```
|
|
211
690
|
|
|
212
|
-
### 4.
|
|
691
|
+
### 4. ✅ Add Clear Comments to i18n Keys
|
|
213
692
|
|
|
214
693
|
```typescript
|
|
694
|
+
// ✅ Good comments: Clearly explain purpose and context
|
|
695
|
+
/**
|
|
696
|
+
* @description Confirmation message when user tries to delete an item
|
|
697
|
+
* @localZh 确定要删除 {{itemName}} 吗?此操作不可撤销。
|
|
698
|
+
* @localEn Are you sure you want to delete {{itemName}}? This action cannot be undone.
|
|
699
|
+
*/
|
|
700
|
+
export const CONFIRM_DELETE_ITEM = 'common:confirm.delete.item';
|
|
701
|
+
|
|
702
|
+
// ❌ Bad comments: No context information
|
|
215
703
|
/**
|
|
216
|
-
* @description
|
|
217
|
-
* @localZh
|
|
218
|
-
* @localEn
|
|
704
|
+
* @description Delete
|
|
705
|
+
* @localZh 删除
|
|
706
|
+
* @localEn Delete
|
|
219
707
|
*/
|
|
220
|
-
export const
|
|
708
|
+
export const DELETE = 'delete';
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### 5. ✅ Unified Naming Conventions
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
// ✅ Recommended naming conventions:
|
|
715
|
+
|
|
716
|
+
// Button text
|
|
717
|
+
export const BUTTON_LOGIN = 'common:button.login';
|
|
718
|
+
export const BUTTON_SUBMIT = 'common:button.submit';
|
|
719
|
+
export const BUTTON_CANCEL = 'common:button.cancel';
|
|
720
|
+
|
|
721
|
+
// Page titles
|
|
722
|
+
export const PAGE_HOME_TITLE = 'common:page.home.title';
|
|
723
|
+
export const PAGE_ABOUT_TITLE = 'common:page.about.title';
|
|
724
|
+
|
|
725
|
+
// Error messages
|
|
726
|
+
export const ERROR_NETWORK = 'common:error.network';
|
|
727
|
+
export const ERROR_INVALID_INPUT = 'common:error.invalid.input';
|
|
728
|
+
|
|
729
|
+
// Message prompts
|
|
730
|
+
export const MESSAGE_SUCCESS = 'common:message.success';
|
|
731
|
+
export const MESSAGE_WARNING = 'common:message.warning';
|
|
732
|
+
|
|
733
|
+
// Confirm dialogs
|
|
734
|
+
export const CONFIRM_DELETE = 'common:confirm.delete';
|
|
735
|
+
export const CONFIRM_LOGOUT = 'common:confirm.logout';
|
|
736
|
+
|
|
737
|
+
// Link text
|
|
738
|
+
export const LINK_FORGOT_PASSWORD = 'common:link.forgot.password';
|
|
739
|
+
export const LINK_PRIVACY_POLICY = 'common:link.privacy.policy';
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### 6. ✅ Avoid Duplicate Definitions
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
// ❌ Wrong: Duplicate definitions of same text
|
|
746
|
+
// config/Identifier/common/common.ts
|
|
747
|
+
export const BUTTON_OK_1 = 'common:button.ok1';
|
|
748
|
+
|
|
749
|
+
// config/Identifier/pages/page.home.ts
|
|
750
|
+
export const BUTTON_OK_2 = 'common:button.ok2';
|
|
751
|
+
|
|
752
|
+
// ✅ Correct: Define once in common file, reference in multiple places
|
|
753
|
+
// config/Identifier/common/common.ts
|
|
754
|
+
export const BUTTON_OK = 'common:button.ok';
|
|
755
|
+
|
|
756
|
+
// Reference in various pages
|
|
757
|
+
import { BUTTON_OK } from '@config/Identifier';
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### 7. ✅ Use Parameterized Text
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
// ❌ Bad: Define different Keys for each case
|
|
764
|
+
export const WELCOME_USER_JOHN = 'common:welcome.john';
|
|
765
|
+
export const WELCOME_USER_MARY = 'common:welcome.mary';
|
|
766
|
+
|
|
767
|
+
// ✅ Good: Use parameters
|
|
768
|
+
/**
|
|
769
|
+
* @description Welcome message with user name
|
|
770
|
+
* @localZh 欢迎,{{name}}!
|
|
771
|
+
* @localEn Welcome, {{name}}!
|
|
772
|
+
*/
|
|
773
|
+
export const WELCOME_USER = 'common:welcome.user';
|
|
221
774
|
|
|
222
775
|
// Usage
|
|
223
|
-
t(
|
|
776
|
+
<h1>{t(WELCOME_USER, { name: 'John' })}</h1>
|
|
777
|
+
<h1>{t(WELCOME_USER, { name: 'Mary' })}</h1>
|
|
224
778
|
```
|
|
225
779
|
|
|
226
|
-
|
|
780
|
+
---
|
|
227
781
|
|
|
228
|
-
|
|
229
|
-
- Define translation keys and comments in Identifier files
|
|
230
|
-
- Reference translations using identifier aliases in code
|
|
231
|
-
- Automatically generate translation files during development server runtime
|
|
782
|
+
## ❓ FAQ
|
|
232
783
|
|
|
233
|
-
|
|
234
|
-
- Automatically check translation completeness
|
|
235
|
-
- Generate production translation files
|
|
236
|
-
- Optimize translation resource loading
|
|
784
|
+
### Q1: Why can't I write text directly?
|
|
237
785
|
|
|
238
|
-
|
|
239
|
-
- Detect language based on URL
|
|
240
|
-
- Load translation resources on demand
|
|
241
|
-
- Respond to language switch events
|
|
786
|
+
**A:**
|
|
242
787
|
|
|
243
|
-
|
|
788
|
+
```typescript
|
|
789
|
+
// ❌ Problems with writing text directly
|
|
790
|
+
<button>Login</button>
|
|
244
791
|
|
|
245
|
-
|
|
792
|
+
// Problem 1: Cannot internationalize
|
|
793
|
+
// If user switches to Chinese, text won't change
|
|
246
794
|
|
|
247
|
-
|
|
795
|
+
// Problem 2: Hard to maintain
|
|
796
|
+
// If want to change "Login" to "Sign In", need to find all places to modify
|
|
248
797
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
- Verify translation files are generated
|
|
252
|
-
- Confirm language detection is working
|
|
798
|
+
// Problem 3: Cannot manage uniformly
|
|
799
|
+
// Can't know how many "Login" buttons exist in the project
|
|
253
800
|
|
|
254
|
-
|
|
801
|
+
// ✅ Advantages of using i18n Key
|
|
802
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
255
803
|
|
|
256
|
-
|
|
804
|
+
// ✅ Automatic internationalization: Switch language to automatically show corresponding text
|
|
805
|
+
// ✅ Easy to maintain: Only need to modify definition in one place
|
|
806
|
+
// ✅ Unified management: All text centralized in Identifier files
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Q2: How to know where a Key is used?
|
|
810
|
+
|
|
811
|
+
**A:**
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
# Search constant name
|
|
815
|
+
grep -r "BUTTON_LOGIN" src/
|
|
257
816
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
817
|
+
# Or use "Find Usages" feature in IDE
|
|
818
|
+
# Can quickly find all places using this Key
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Q3: What if I forget to define an i18n Key?
|
|
261
822
|
|
|
262
|
-
|
|
823
|
+
**A:** TypeScript will error at compile time:
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
// ❌ Using undefined Key
|
|
827
|
+
<button>{t(BUTTON_NOT_EXIST)}</button>
|
|
828
|
+
// TypeScript error: Cannot find name 'BUTTON_NOT_EXIST'
|
|
829
|
+
|
|
830
|
+
// ✅ Correct: Define Key first
|
|
831
|
+
export const BUTTON_NOT_EXIST = 'common:button.not.exist';
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Q4: How to handle dynamic text?
|
|
835
|
+
|
|
836
|
+
**A:** Use parameters:
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
/**
|
|
840
|
+
* @description User greeting with name and time
|
|
841
|
+
* @localZh {{name}},{{time}}好!
|
|
842
|
+
* @localEn Good {{time}}, {{name}}!
|
|
843
|
+
*/
|
|
844
|
+
export const GREETING_WITH_TIME = 'common:greeting.with.time';
|
|
845
|
+
|
|
846
|
+
// Usage
|
|
847
|
+
<h1>{t(GREETING_WITH_TIME, { name: 'John', time: 'morning' })}</h1>
|
|
848
|
+
// Chinese: John,morning好!
|
|
849
|
+
// English: Good morning, John!
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### Q5: How are translation files generated?
|
|
853
|
+
|
|
854
|
+
**A:**
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
1. Developer defines Keys and translation comments in Identifier files
|
|
858
|
+
↓
|
|
859
|
+
2. ts2locales plugin scans TypeScript files
|
|
860
|
+
↓
|
|
861
|
+
3. Extract @localZh and @localEn from comments
|
|
862
|
+
↓
|
|
863
|
+
4. Auto-generate public/locales/{lng}/common.json
|
|
864
|
+
↓
|
|
865
|
+
5. i18next loads these JSON files at runtime
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Q6: Can I use translated text directly in code?
|
|
869
|
+
|
|
870
|
+
**A:**
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
// ❌ Not recommended: Skip i18n system
|
|
874
|
+
const loginText = 'Login'; // Hard-coded
|
|
875
|
+
<button>{loginText}</button>
|
|
876
|
+
|
|
877
|
+
// ✅ Recommended: Always use i18n Key
|
|
878
|
+
const loginText = t(BUTTON_LOGIN); // Through i18n system
|
|
879
|
+
<button>{loginText}</button>
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### Q7: How to test i18n?
|
|
883
|
+
|
|
884
|
+
**A:**
|
|
885
|
+
|
|
886
|
+
```typescript
|
|
887
|
+
// __tests__/i18n.test.ts
|
|
888
|
+
import { I18nService } from '@/base/services/I18nService';
|
|
889
|
+
import { BUTTON_LOGIN } from '@config/Identifier';
|
|
890
|
+
|
|
891
|
+
describe('I18n', () => {
|
|
892
|
+
let i18nService: I18nService;
|
|
893
|
+
|
|
894
|
+
beforeEach(() => {
|
|
895
|
+
i18nService = new I18nService('/zh/home');
|
|
896
|
+
i18nService.onBefore();
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it('should translate to Chinese', () => {
|
|
900
|
+
i18nService.changeLanguage('zh');
|
|
901
|
+
expect(i18nService.t(BUTTON_LOGIN)).toBe('登录');
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should translate to English', () => {
|
|
905
|
+
i18nService.changeLanguage('en');
|
|
906
|
+
expect(i18nService.t(BUTTON_LOGIN)).toBe('Login');
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it('should support parameters', () => {
|
|
910
|
+
const result = i18nService.t(WELCOME_USER, { name: 'John' });
|
|
911
|
+
expect(result).toContain('John');
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## 📚 Related Documentation
|
|
919
|
+
|
|
920
|
+
- [Project Architecture Design](./index.md) - Understand overall architecture
|
|
921
|
+
- [Bootstrap Initializer](./bootstrap.md) - I18nService is one of the Bootstrap plugins
|
|
922
|
+
- [Configuration-Driven Development](./#configuration-driven-development) - i18n Key is a practice of configuration-driven development
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## 🎉 Summary
|
|
927
|
+
|
|
928
|
+
Core philosophy of the internationalization system:
|
|
929
|
+
|
|
930
|
+
1. **Unique Identifier** 🔑 - Every text has a unique i18n Key
|
|
931
|
+
2. **Never Hard-code** 🚫 - All text in project must use i18n Keys
|
|
932
|
+
3. **Reduce Thinking** 🧠 - Developers only need to know variable names, don't need to remember string paths
|
|
933
|
+
4. **Centralized Management** 📦 - All Key definitions in Identifier directory
|
|
934
|
+
5. **Auto-generation** ⚡ - Translation files auto-generated from comments
|
|
935
|
+
6. **Type Safety** 🔒 - TypeScript provides complete type checking
|
|
936
|
+
7. **Easy to Maintain** 🛠️ - Only need to modify translation in one place
|
|
937
|
+
8. **IDE Friendly** 💻 - Complete auto-completion and type hints
|
|
938
|
+
|
|
939
|
+
**Remember two core principles:**
|
|
940
|
+
|
|
941
|
+
1. **Never write text directly in code!**
|
|
942
|
+
2. **Developers don't need to remember translation strings, just variable names!**
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
// ❌ Never do this
|
|
946
|
+
<button>Login</button>
|
|
947
|
+
<h1>Welcome back</h1>
|
|
948
|
+
<p>{t('common:confirm.delete')}</p> // Don't write strings directly either
|
|
949
|
+
|
|
950
|
+
// ✅ Always do this
|
|
951
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
952
|
+
<h1>{t(MESSAGE_WELCOME)}</h1>
|
|
953
|
+
<p>{t(CONFIRM_DELETE)}</p> // Use variables, IDE will auto-complete
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
**Development Workflow:**
|
|
957
|
+
|
|
958
|
+
```typescript
|
|
959
|
+
// 1. Type variable name prefix
|
|
960
|
+
t(BUTTON_
|
|
961
|
+
|
|
962
|
+
// 2. IDE automatically suggests all available Keys
|
|
963
|
+
// BUTTON_LOGIN
|
|
964
|
+
// BUTTON_SUBMIT
|
|
965
|
+
// BUTTON_CANCEL
|
|
966
|
+
// ...
|
|
967
|
+
|
|
968
|
+
// 3. Select needed Key, done!
|
|
969
|
+
t(BUTTON_LOGIN)
|
|
970
|
+
|
|
971
|
+
// Don't need to remember 'common:button.login'
|
|
972
|
+
// Don't need to care about translation file structure
|
|
973
|
+
// Don't need to worry about spelling errors (TypeScript will check)
|
|
974
|
+
```
|
|
263
975
|
|
|
264
|
-
|
|
976
|
+
---
|
|
265
977
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
- Avoid duplicate translation keys
|
|
978
|
+
**Feedback:**
|
|
979
|
+
If you have any questions or suggestions about the internationalization system, please discuss in the team channel or submit an Issue.
|