@qlover/create-app 0.7.15 → 0.8.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 +4 -0
- 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/config/IOCIdentifier.ts +8 -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/index.ts +1 -9
- package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -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/{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 +66 -69
- 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/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/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/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/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 +190 -179
- package/dist/templates/react-app/public/locales/zh/common.json +190 -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/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/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/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
|
-
#
|
|
1
|
+
# 国际化 (i18n)
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 📋 目录
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [核心理念](#-核心理念) - 绝不硬编码,只用变量
|
|
6
|
+
- [什么是 i18n Key](#-什么是-i18n-key) - 唯一标识符
|
|
7
|
+
- [为什么使用 i18n Key](#-为什么使用-i18n-key) - 减少思考负担
|
|
8
|
+
- [项目中的实现](#-项目中的实现) - Bootstrap 插件
|
|
9
|
+
- [使用方式](#-使用方式) - 实战指南
|
|
10
|
+
- [最佳实践](#-最佳实践) - 7 条核心实践
|
|
11
|
+
- [常见问题](#-常见问题) - FAQ
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
- **TypeScript 注释**:通过注释自动生成翻译资源
|
|
9
|
-
- **类型安全**:完整的类型检查和自动补全
|
|
10
|
-
- **路由集成**:支持 URL 路径中的语言参数
|
|
11
|
-
- **自动生成**:自动从源码生成翻译文件
|
|
12
|
-
- **开发友好**:完整的开发工具支持
|
|
13
|
+
---
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## 🎯 核心理念
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
> **🚨 重要原则:项目中所有需要显示的文字、词语、句子,都必须使用 i18n Key,绝不允许硬编码文本!**
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
> **⭐ 核心优势:开发者不需要记住复杂的翻译字符串,只需要知道变量名,IDE 会提供自动补全!**
|
|
20
|
+
|
|
21
|
+
### 核心概念
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
❌ 硬编码文本 ✅ i18n Key(变量)
|
|
25
|
+
"登录" → BUTTON_LOGIN (不需要记住 'common:button.login')
|
|
26
|
+
"欢迎回来" → MESSAGE_WELCOME (不需要记住 'common:message.welcome')
|
|
27
|
+
"确定要删除吗?" → CONFIRM_DELETE (不需要记住 'common:confirm.delete')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**每个文本都有唯一的标识符:**
|
|
19
31
|
|
|
20
32
|
```typescript
|
|
21
|
-
//
|
|
33
|
+
// ❌ 错误:直接写文字或字符串
|
|
34
|
+
<button>登录</button>
|
|
35
|
+
<h1>欢迎回来</h1>
|
|
36
|
+
<p>{t('common:confirm.delete')}</p> // 也不要写字符串
|
|
37
|
+
|
|
38
|
+
// ✅ 正确:使用 i18n Key 变量
|
|
39
|
+
<button>{t(BUTTON_LOGIN)}</button> // 只需记住变量名
|
|
40
|
+
<h1>{t(MESSAGE_WELCOME)}</h1> // IDE 会自动补全
|
|
41
|
+
<p>{t(CONFIRM_DELETE)}</p> // TypeScript 会检查拼写
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔑 什么是 i18n Key
|
|
47
|
+
|
|
48
|
+
i18n Key 是**每个文本的唯一标识符**,就像每个人都有身份证号一样。
|
|
49
|
+
|
|
50
|
+
### 基本概念
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// i18n Key 定义(在 config/Identifier/ 目录下)
|
|
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
|
+
// ↑ 常量名 ↑ 标识符字符串
|
|
62
|
+
// 用于代码中引用 存储在翻译文件中的键名
|
|
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
|
+
**结构说明:**
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
'common:button.login'
|
|
76
|
+
↑ ↑ ↑
|
|
77
|
+
命名空间 分类 具体标识
|
|
78
|
+
```
|
|
39
79
|
|
|
40
|
-
|
|
80
|
+
### 命名规范
|
|
41
81
|
|
|
42
82
|
```typescript
|
|
43
|
-
|
|
83
|
+
// ✅ 好的命名:清晰、结构化
|
|
84
|
+
export const BUTTON_LOGIN = 'common:button.login'; // 按钮文本
|
|
85
|
+
export const BUTTON_SUBMIT = 'common:button.submit'; // 按钮文本
|
|
86
|
+
export const MESSAGE_WELCOME = 'common:message.welcome'; // 消息文本
|
|
87
|
+
export const CONFIRM_DELETE = 'common:confirm.delete'; // 确认对话框
|
|
88
|
+
export const ERROR_NETWORK = 'common:error.network'; // 错误信息
|
|
89
|
+
export const PAGE_HOME_TITLE = 'common:page.home.title'; // 页面标题
|
|
90
|
+
|
|
91
|
+
// ❌ 不好的命名:模糊、无结构
|
|
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
|
+
## 🤔 为什么使用 i18n Key
|
|
100
|
+
|
|
101
|
+
### 问题:硬编码文本的痛点
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ❌ 传统方式:文本硬编码
|
|
105
|
+
|
|
106
|
+
function LoginPage() {
|
|
107
|
+
return (
|
|
108
|
+
<div>
|
|
109
|
+
<h1>登录</h1>
|
|
110
|
+
<button>登录</button>
|
|
111
|
+
<p>欢迎回来!</p>
|
|
112
|
+
<span>忘记密码?</span>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 😰 问题 1:国际化困难
|
|
118
|
+
// 如果要支持英文,需要修改大量代码
|
|
119
|
+
|
|
120
|
+
// 😰 问题 2:维护困难
|
|
121
|
+
// 同样的文本可能在多个地方使用,修改时容易遗漏
|
|
122
|
+
|
|
123
|
+
// 😰 问题 3:难以统一管理
|
|
124
|
+
// 无法统计项目中有多少个文本,哪些需要翻译
|
|
125
|
+
|
|
126
|
+
// 😰 问题 4:搜索困难
|
|
127
|
+
// 想找到某个文本在哪里使用,只能全局搜索字符串
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 解决方案:使用 i18n Key
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// ✅ 使用 i18n Key
|
|
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
|
+
// ✅ 优势 1:自动国际化
|
|
157
|
+
// 根据用户语言自动显示对应文本
|
|
158
|
+
|
|
159
|
+
// ✅ 优势 2:集中管理
|
|
160
|
+
// 所有文本在 Identifier 文件中统一管理
|
|
161
|
+
|
|
162
|
+
// ✅ 优势 3:易于维护
|
|
163
|
+
// 修改文本只需修改一处
|
|
164
|
+
|
|
165
|
+
// ✅ 优势 4:类型安全
|
|
166
|
+
// TypeScript 会检查 Key 是否存在
|
|
167
|
+
|
|
168
|
+
// ✅ 优势 5:减少思考 ⭐
|
|
169
|
+
// 开发者只需要知道 BUTTON_LOGIN 是一个变量
|
|
170
|
+
// 不需要记住 'common:button.login' 这个字符串
|
|
171
|
+
// IDE 会提供自动补全,写错了会立即报错
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 对比总结
|
|
175
|
+
|
|
176
|
+
| 特性 | 硬编码文本 | i18n Key |
|
|
177
|
+
| ------------ | --------------------- | ----------------------- |
|
|
178
|
+
| **国际化** | ❌ 困难(需要改代码) | ✅ 自动(切换语言即可) |
|
|
179
|
+
| **维护性** | ❌ 差(分散各处) | ✅ 好(集中管理) |
|
|
180
|
+
| **重用性** | ❌ 差(复制粘贴) | ✅ 好(引用 Key) |
|
|
181
|
+
| **搜索性** | ❌ 困难(字符串搜索) | ✅ 简单(搜索常量) |
|
|
182
|
+
| **统计性** | ❌ 不可能 | ✅ 容易(统计 Key) |
|
|
183
|
+
| **类型安全** | ❌ 没有 | ✅ 有(TypeScript) |
|
|
184
|
+
| **心智负担** | ❌ 高(需记住文字) | ✅ 低(只需记变量名) |
|
|
185
|
+
| **IDE 支持** | ❌ 无自动补全 | ✅ 完整自动补全 |
|
|
186
|
+
|
|
187
|
+
### ⭐ 核心优势:减少开发者思考
|
|
188
|
+
|
|
189
|
+
**最重要的优势:开发者不需要关心具体的 i18n 字符串是什么!**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// ❌ 传统方式:需要记住复杂的字符串
|
|
193
|
+
function LoginPage() {
|
|
194
|
+
const { t } = useTranslation();
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
{/* 😰 需要记住 'common:button.login' */}
|
|
199
|
+
<button>{t('common:button.login')}</button>
|
|
200
|
+
|
|
201
|
+
{/* 😰 需要记住 'page.login.title' */}
|
|
202
|
+
<h1>{t('page.login.title')}</h1>
|
|
203
|
+
|
|
204
|
+
{/* 😰 写错了也不会报错 */}
|
|
205
|
+
<p>{t('page.login.welcom')}</p> {/* welcom → welcome,拼写错误 */}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ✅ i18n Key 方式:只需要知道变量名
|
|
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
|
+
{/* ✅ 只需要记住 BUTTON_LOGIN,IDE 会自动补全 */}
|
|
219
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
220
|
+
|
|
221
|
+
{/* ✅ 输入 PAGE_ 后,IDE 会提示所有可用的 Key */}
|
|
222
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
223
|
+
|
|
224
|
+
{/* ✅ 写错了 TypeScript 会立即报错 */}
|
|
225
|
+
<p>{t(PAGE_LOGIN_WELCOM)}</p> {/* ❌ TS 错误:找不到变量 */}
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**开发体验对比:**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// ❌ 使用字符串的开发流程:
|
|
235
|
+
// 1. 😰 查看翻译文件,找到对应的 key
|
|
236
|
+
// 2. 😰 记住 key 的完整路径(如 'common:page.login.title')
|
|
237
|
+
// 3. 😰 在代码中手动输入字符串
|
|
238
|
+
// 4. 😰 可能输入错误,但编译器不会报错
|
|
239
|
+
// 5. 😰 运行时才发现翻译没有生效
|
|
240
|
+
|
|
241
|
+
// ✅ 使用 i18n Key 的开发流程:
|
|
242
|
+
// 1. 😊 输入变量名前缀(如 BUTTON_)
|
|
243
|
+
// 2. 😊 IDE 自动提示所有可用的 Key
|
|
244
|
+
// 3. 😊 选择需要的 Key,IDE 自动补全
|
|
245
|
+
// 4. 😊 如果输入错误,TypeScript 立即报错
|
|
246
|
+
// 5. 😊 编译期就能确保 Key 的正确性
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**认知负担对比:**
|
|
250
|
+
|
|
251
|
+
| 开发者需要记住的内容 | 字符串方式 | i18n Key 方式 |
|
|
252
|
+
| -------------------- | --------------------------------------- | --------------- |
|
|
253
|
+
| **翻译文件结构** | ✅ 必须记住(如 `common:button.login`) | ❌ 不需要记 |
|
|
254
|
+
| **命名空间** | ✅ 必须记住(如 `common:`, `page:`) | ❌ 不需要记 |
|
|
255
|
+
| **字符串路径** | ✅ 必须记住完整路径 | ❌ 不需要记 |
|
|
256
|
+
| **变量名** | ❌ 没有变量 | ✅ 只需记变量名 |
|
|
257
|
+
| **IDE 提示** | ❌ 没有 | ✅ 完整提示 |
|
|
258
|
+
|
|
259
|
+
**实际开发场景:**
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// 场景 1:新写一个按钮
|
|
263
|
+
|
|
264
|
+
// ❌ 字符串方式(需要思考很多)
|
|
265
|
+
// 1. 这个按钮的翻译 key 应该叫什么?
|
|
266
|
+
// 2. 应该放在哪个命名空间?common? page?
|
|
267
|
+
// 3. 路径应该是什么?button.submit? submit.button?
|
|
268
|
+
// 4. 最终写出:t('common:button.submit')
|
|
269
|
+
<button>{t('common:button.submit')}</button>
|
|
270
|
+
|
|
271
|
+
// ✅ i18n Key 方式(只需要搜索)
|
|
272
|
+
// 1. 输入 BUTTON_
|
|
273
|
+
// 2. IDE 显示所有可用的按钮 Key
|
|
274
|
+
// 3. 看到 BUTTON_SUBMIT,选择它
|
|
275
|
+
// 4. 完成!
|
|
276
|
+
<button>{t(BUTTON_SUBMIT)}</button>
|
|
277
|
+
|
|
278
|
+
// 场景 2:要改另一个页面的文字
|
|
279
|
+
|
|
280
|
+
// ❌ 字符串方式
|
|
281
|
+
// 1. 查看那个页面的代码
|
|
282
|
+
// 2. 找到 t('page.home.welcome')
|
|
283
|
+
// 3. 记住这个字符串
|
|
284
|
+
// 4. 在翻译文件中搜索并修改
|
|
285
|
+
|
|
286
|
+
// ✅ i18n Key 方式
|
|
287
|
+
// 1. 看到代码中的 PAGE_HOME_WELCOME
|
|
288
|
+
// 2. 直接跳转到定义(IDE 的 Go to Definition)
|
|
289
|
+
// 3. 修改注释中的翻译
|
|
290
|
+
// 4. 自动生成新的翻译文件
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**总结:**
|
|
294
|
+
|
|
295
|
+
使用 i18n Key 让开发者:
|
|
296
|
+
|
|
297
|
+
- ✅ **不需要记住** 复杂的字符串路径
|
|
298
|
+
- ✅ **不需要记住** 翻译文件的结构
|
|
299
|
+
- ✅ **不需要关心** 具体的翻译键名
|
|
300
|
+
- ✅ **只需要知道** 这是一个变量
|
|
301
|
+
- ✅ **依靠 IDE** 提供的自动补全和类型检查
|
|
302
|
+
- ✅ **专注于** 业务逻辑,而不是翻译细节
|
|
303
|
+
|
|
304
|
+
> 💡 **核心理念:让开发者把精力用在业务逻辑上,而不是记忆翻译字符串!**
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 🛠️ 项目中的实现
|
|
309
|
+
|
|
310
|
+
### 1. 文件结构
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
config/
|
|
314
|
+
└── Identifier/ # i18n Key 定义目录
|
|
315
|
+
├── index.ts # 导出所有 Key
|
|
316
|
+
├── common/ # 公共文本
|
|
317
|
+
│ ├── index.ts
|
|
318
|
+
│ ├── common.ts # 通用文本(按钮、消息等)
|
|
319
|
+
│ └── common.error.ts # 错误信息
|
|
320
|
+
└── pages/ # 页面文本
|
|
321
|
+
├── index.ts
|
|
322
|
+
├── page.home.ts # 首页文本
|
|
323
|
+
├── page.login.ts # 登录页文本
|
|
324
|
+
└── page.about.ts # 关于页文本
|
|
325
|
+
|
|
326
|
+
public/
|
|
327
|
+
└── locales/ # 生成的翻译文件
|
|
328
|
+
├── zh/
|
|
329
|
+
│ └── common.json # 中文翻译
|
|
330
|
+
└── en/
|
|
331
|
+
└── common.json # 英文翻译
|
|
332
|
+
|
|
333
|
+
src/
|
|
334
|
+
└── uikit/
|
|
335
|
+
└── hooks/
|
|
336
|
+
└── useAppTranslation.ts # 翻译 Hook
|
|
62
337
|
```
|
|
63
338
|
|
|
64
|
-
###
|
|
339
|
+
### 2. i18n 配置
|
|
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', // 默认语言
|
|
70
345
|
debug: false,
|
|
71
346
|
interpolation: {
|
|
72
347
|
escapeValue: false
|
|
73
348
|
},
|
|
74
|
-
ns: ['common'],
|
|
349
|
+
ns: ['common'], // 命名空间
|
|
75
350
|
defaultNS: 'common',
|
|
76
351
|
backend: {
|
|
77
|
-
loadPath: '/locales/{{lng}}/{{ns}}.json'
|
|
352
|
+
loadPath: '/locales/{{lng}}/{{ns}}.json' // 翻译文件路径
|
|
78
353
|
},
|
|
79
|
-
supportedLngs: ['en', 'zh']
|
|
354
|
+
supportedLngs: ['en', 'zh'] // 支持的语言
|
|
80
355
|
} as const;
|
|
81
356
|
```
|
|
82
357
|
|
|
83
|
-
|
|
358
|
+
### 3. I18nService(Bootstrap 插件)
|
|
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
|
+
* 在 Bootstrap 启动时初始化 i18n
|
|
371
|
+
*/
|
|
372
|
+
onBefore(): void {
|
|
373
|
+
i18n
|
|
374
|
+
.use(HttpApi) // 加载翻译文件
|
|
375
|
+
.use(LanguageDetector) // 语言检测
|
|
376
|
+
.use(initReactI18next) // React 集成
|
|
377
|
+
.init(i18nConfig); // 初始化配置
|
|
378
|
+
|
|
379
|
+
// 添加自定义语言检测器(从 URL 路径检测)
|
|
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; // 从 URL 中提取语言(如 /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
|
+
* 切换语言
|
|
401
|
+
*/
|
|
402
|
+
async changeLanguage(language: string): Promise<void> {
|
|
403
|
+
await i18n.changeLanguage(language);
|
|
404
|
+
localStorage.setItem('i18nextLng', language);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 翻译文本
|
|
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. 自动生成翻译文件
|
|
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 插件:自动从 TypeScript 注释生成翻译文件
|
|
425
|
+
ts2Locales({
|
|
426
|
+
locales: ['en', 'zh'],
|
|
427
|
+
options: generateTs2LocalesOptions() // 配置生成规则
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**工作原理:**
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// 1. 在 Identifier 文件中定义 Key
|
|
437
|
+
/**
|
|
438
|
+
* @description Login button text
|
|
439
|
+
* @localZh 登录
|
|
440
|
+
* @localEn Login
|
|
441
|
+
*/
|
|
442
|
+
export const BUTTON_LOGIN = 'common:button.login';
|
|
443
|
+
|
|
444
|
+
// 2. ts2locales 插件自动生成翻译文件
|
|
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
|
+
## 📝 使用方式
|
|
464
|
+
|
|
465
|
+
### 1. 定义 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
|
+
**注释规范:**
|
|
493
|
+
|
|
494
|
+
- `@description`:说明文本用途(英文)
|
|
495
|
+
- `@localZh`:中文翻译
|
|
496
|
+
- `@localEn`:英文翻译
|
|
106
497
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
498
|
+
### 2. 在 UI 组件中使用
|
|
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
|
+
{/* ✅ 简单文本 */}
|
|
516
|
+
<h1>{t(PAGE_HOME_TITLE)}</h1>
|
|
517
|
+
<p>{t(PAGE_HOME_WELCOME)}</p>
|
|
518
|
+
|
|
519
|
+
{/* ✅ 带参数的文本 */}
|
|
520
|
+
<p>{t(PAGE_HOME_GREETING, { name: userName })}</p>
|
|
118
521
|
</div>
|
|
119
522
|
);
|
|
120
523
|
}
|
|
121
524
|
```
|
|
122
525
|
|
|
123
|
-
### 3.
|
|
526
|
+
### 3. 在服务中使用
|
|
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
|
+
// ✅ 在服务中使用 i18n
|
|
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.
|
|
554
|
+
### 4. 切换语言
|
|
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
|
+
// ✅ 切换语言
|
|
150
566
|
i18nService.changeLanguage(lang);
|
|
151
567
|
};
|
|
152
568
|
|
|
@@ -159,110 +575,405 @@ function LanguageSwitcher() {
|
|
|
159
575
|
}
|
|
160
576
|
```
|
|
161
577
|
|
|
162
|
-
|
|
578
|
+
### 5. 复杂场景
|
|
163
579
|
|
|
164
|
-
|
|
580
|
+
#### 场景 1:动态文本
|
|
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
|
+
// 使用
|
|
591
|
+
<p>{t(UPLOAD_PROGRESS, { current: 3, total: 10 })}</p>
|
|
592
|
+
// 中文:已上传 3 / 10 个文件
|
|
593
|
+
// 英文:Uploaded 3 / 10 files
|
|
594
|
+
```
|
|
172
595
|
|
|
173
|
-
|
|
174
|
-
AUTH_LOGIN_BUTTON;
|
|
175
|
-
AUTH_LOGOUT_CONFIRM;
|
|
596
|
+
#### 场景 2:复数形式
|
|
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 // 公共文本
|
|
187
|
-
│ ├── page.ts // 页面文本
|
|
188
|
-
│ ├── error.ts // 错误信息
|
|
189
|
-
│ └── auth.ts // 认证相关
|
|
190
|
-
```
|
|
606
|
+
// 使用
|
|
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
|
+
#### 场景 3:HTML 内容
|
|
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
|
+
// 使用(React 中需要使用 Trans 组件)
|
|
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
|
+
## 💎 最佳实践
|
|
632
|
+
|
|
633
|
+
### 1. ✅ 所有文本都使用 i18n Key
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
// ❌ 错误:混用硬编码和 i18n Key
|
|
637
|
+
function LoginForm() {
|
|
638
|
+
return (
|
|
639
|
+
<form>
|
|
640
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
641
|
+
<button>登录</button> {/* ❌ 硬编码 */}
|
|
642
|
+
<a href="/forgot">忘记密码?</a> {/* ❌ 硬编码 */}
|
|
643
|
+
</form>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ✅ 正确:所有文本都使用 i18n Key
|
|
648
|
+
function LoginForm() {
|
|
649
|
+
return (
|
|
650
|
+
<form>
|
|
651
|
+
<h1>{t(PAGE_LOGIN_TITLE)}</h1>
|
|
652
|
+
<button>{t(BUTTON_LOGIN)}</button> {/* ✅ 使用 Key */}
|
|
653
|
+
<a href="/forgot">{t(LINK_FORGOT_PASSWORD)}</a> {/* ✅ 使用 Key */}
|
|
654
|
+
</form>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
200
657
|
```
|
|
201
658
|
|
|
202
|
-
###
|
|
659
|
+
### 2. ✅ 合理组织 i18n Key
|
|
203
660
|
|
|
204
661
|
```typescript
|
|
205
|
-
//
|
|
206
|
-
|
|
662
|
+
// ✅ 好的组织:按功能模块分类
|
|
663
|
+
config/Identifier/
|
|
664
|
+
├── common/
|
|
665
|
+
│ ├── common.ts # 通用文本(按钮、标签等)
|
|
666
|
+
│ └── common.error.ts # 错误信息
|
|
667
|
+
├── pages/
|
|
668
|
+
│ ├── page.home.ts # 首页文本
|
|
669
|
+
│ ├── page.login.ts # 登录页文本
|
|
670
|
+
│ └── page.about.ts # 关于页文本
|
|
671
|
+
└── components/
|
|
672
|
+
├── component.header.ts # 头部组件文本
|
|
673
|
+
└── component.footer.ts # 底部组件文本
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### 3. ✅ 使用语义化的命名
|
|
207
677
|
|
|
208
|
-
|
|
209
|
-
|
|
678
|
+
```typescript
|
|
679
|
+
// ✅ 好的命名:清晰表达含义
|
|
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
|
+
// ❌ 不好的命名:含义不清
|
|
686
|
+
export const BTN1 = 'btn1';
|
|
687
|
+
export const TEXT = 'text';
|
|
688
|
+
export const MSG = 'msg';
|
|
210
689
|
```
|
|
211
690
|
|
|
212
|
-
### 4.
|
|
691
|
+
### 4. ✅ 为 i18n Key 添加清晰的注释
|
|
213
692
|
|
|
214
693
|
```typescript
|
|
694
|
+
// ✅ 好的注释:清晰说明用途和上下文
|
|
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
|
+
// ❌ 不好的注释:没有上下文信息
|
|
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. ✅ 统一命名规范
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
// ✅ 推荐的命名规范:
|
|
715
|
+
|
|
716
|
+
// 按钮文本
|
|
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
|
+
// 页面标题
|
|
722
|
+
export const PAGE_HOME_TITLE = 'common:page.home.title';
|
|
723
|
+
export const PAGE_ABOUT_TITLE = 'common:page.about.title';
|
|
724
|
+
|
|
725
|
+
// 错误信息
|
|
726
|
+
export const ERROR_NETWORK = 'common:error.network';
|
|
727
|
+
export const ERROR_INVALID_INPUT = 'common:error.invalid.input';
|
|
728
|
+
|
|
729
|
+
// 消息提示
|
|
730
|
+
export const MESSAGE_SUCCESS = 'common:message.success';
|
|
731
|
+
export const MESSAGE_WARNING = 'common:message.warning';
|
|
732
|
+
|
|
733
|
+
// 确认对话框
|
|
734
|
+
export const CONFIRM_DELETE = 'common:confirm.delete';
|
|
735
|
+
export const CONFIRM_LOGOUT = 'common:confirm.logout';
|
|
736
|
+
|
|
737
|
+
// 链接文本
|
|
738
|
+
export const LINK_FORGOT_PASSWORD = 'common:link.forgot.password';
|
|
739
|
+
export const LINK_PRIVACY_POLICY = 'common:link.privacy.policy';
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### 6. ✅ 避免重复定义
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
// ❌ 错误:重复定义相同的文本
|
|
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
|
+
// ✅ 正确:在公共文件中定义一次,多处引用
|
|
753
|
+
// config/Identifier/common/common.ts
|
|
754
|
+
export const BUTTON_OK = 'common:button.ok';
|
|
755
|
+
|
|
756
|
+
// 在各个页面中引用
|
|
757
|
+
import { BUTTON_OK } from '@config/Identifier';
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### 7. ✅ 使用参数化文本
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
// ❌ 不好:为每种情况定义不同的 Key
|
|
764
|
+
export const WELCOME_USER_JOHN = 'common:welcome.john';
|
|
765
|
+
export const WELCOME_USER_MARY = 'common:welcome.mary';
|
|
766
|
+
|
|
767
|
+
// ✅ 好:使用参数
|
|
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
|
// 使用
|
|
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
|
-
- 在 Identifier 文件中定义翻译键和注释
|
|
230
|
-
- 使用标识符别名在代码中引用翻译
|
|
231
|
-
- 运行开发服务器时自动生成翻译文件
|
|
782
|
+
## ❓ 常见问题
|
|
232
783
|
|
|
233
|
-
|
|
234
|
-
- 自动检查翻译完整性
|
|
235
|
-
- 生成生产环境翻译文件
|
|
236
|
-
- 优化翻译资源加载
|
|
784
|
+
### Q1: 为什么不能直接写文字?
|
|
237
785
|
|
|
238
|
-
|
|
239
|
-
- 根据 URL 检测语言
|
|
240
|
-
- 按需加载翻译资源
|
|
241
|
-
- 响应语言切换事件
|
|
786
|
+
**A:**
|
|
242
787
|
|
|
243
|
-
|
|
788
|
+
```typescript
|
|
789
|
+
// ❌ 直接写文字的问题
|
|
790
|
+
<button>登录</button>
|
|
244
791
|
|
|
245
|
-
|
|
792
|
+
// 问题 1:无法国际化
|
|
793
|
+
// 如果用户切换到英文,文字不会改变
|
|
246
794
|
|
|
247
|
-
|
|
795
|
+
// 问题 2:难以维护
|
|
796
|
+
// 如果要把"登录"改成"立即登录",需要找到所有地方修改
|
|
248
797
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
- 验证翻译文件是否生成
|
|
252
|
-
- 确认语言检测正确
|
|
798
|
+
// 问题 3:无法统一管理
|
|
799
|
+
// 无法知道项目中有多少个"登录"按钮
|
|
253
800
|
|
|
254
|
-
|
|
801
|
+
// ✅ 使用 i18n Key 的优势
|
|
802
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
255
803
|
|
|
256
|
-
|
|
804
|
+
// ✅ 自动国际化:切换语言自动显示对应文本
|
|
805
|
+
// ✅ 易于维护:只需修改一处定义
|
|
806
|
+
// ✅ 统一管理:所有文本集中在 Identifier 文件中
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Q2: 如何知道某个 Key 在哪里使用?
|
|
810
|
+
|
|
811
|
+
**A:**
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
# 搜索常量名
|
|
815
|
+
grep -r "BUTTON_LOGIN" src/
|
|
257
816
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
817
|
+
# 或者在 IDE 中使用 "Find Usages" 功能
|
|
818
|
+
# 可以快速找到所有使用该 Key 的地方
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### Q3: 忘记定义 i18n Key 怎么办?
|
|
261
822
|
|
|
262
|
-
|
|
823
|
+
**A:** TypeScript 会在编译时报错:
|
|
824
|
+
|
|
825
|
+
```typescript
|
|
826
|
+
// ❌ 使用未定义的 Key
|
|
827
|
+
<button>{t(BUTTON_NOT_EXIST)}</button>
|
|
828
|
+
// TypeScript 错误:Cannot find name 'BUTTON_NOT_EXIST'
|
|
829
|
+
|
|
830
|
+
// ✅ 正确:先定义 Key
|
|
831
|
+
export const BUTTON_NOT_EXIST = 'common:button.not.exist';
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Q4: 如何处理动态文本?
|
|
835
|
+
|
|
836
|
+
**A:** 使用参数:
|
|
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
|
+
// 使用
|
|
847
|
+
<h1>{t(GREETING_WITH_TIME, { name: 'John', time: 'morning' })}</h1>
|
|
848
|
+
// 中文:John,morning好!
|
|
849
|
+
// 英文:Good morning, John!
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### Q5: 翻译文件是如何生成的?
|
|
853
|
+
|
|
854
|
+
**A:**
|
|
855
|
+
|
|
856
|
+
```
|
|
857
|
+
1. 开发者在 Identifier 文件中定义 Key 和翻译注释
|
|
858
|
+
↓
|
|
859
|
+
2. ts2locales 插件扫描 TypeScript 文件
|
|
860
|
+
↓
|
|
861
|
+
3. 从注释中提取 @localZh 和 @localEn
|
|
862
|
+
↓
|
|
863
|
+
4. 自动生成 public/locales/{lng}/common.json
|
|
864
|
+
↓
|
|
865
|
+
5. i18next 在运行时加载这些 JSON 文件
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Q6: 可以在代码中直接使用翻译后的文本吗?
|
|
869
|
+
|
|
870
|
+
**A:**
|
|
871
|
+
|
|
872
|
+
```typescript
|
|
873
|
+
// ❌ 不推荐:跳过 i18n 系统
|
|
874
|
+
const loginText = '登录'; // 硬编码
|
|
875
|
+
<button>{loginText}</button>
|
|
876
|
+
|
|
877
|
+
// ✅ 推荐:始终使用 i18n Key
|
|
878
|
+
const loginText = t(BUTTON_LOGIN); // 通过 i18n 系统
|
|
879
|
+
<button>{loginText}</button>
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### Q7: 如何测试 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
|
+
## 📚 相关文档
|
|
919
|
+
|
|
920
|
+
- [项目架构设计](./index.md) - 了解整体架构
|
|
921
|
+
- [Bootstrap 启动器](./bootstrap.md) - I18nService 是 Bootstrap 插件之一
|
|
922
|
+
- [配置驱动开发](./#配置驱动开发) - i18n Key 是配置驱动的实践
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## 🎉 总结
|
|
927
|
+
|
|
928
|
+
国际化系统的核心理念:
|
|
929
|
+
|
|
930
|
+
1. **唯一标识符** 🔑 - 每个文本都有唯一的 i18n Key
|
|
931
|
+
2. **绝不硬编码** 🚫 - 项目中所有文本都必须使用 i18n Key
|
|
932
|
+
3. **减少思考** 🧠 - 开发者只需知道变量名,不需要记住字符串路径
|
|
933
|
+
4. **集中管理** 📦 - 所有 Key 定义在 Identifier 目录中
|
|
934
|
+
5. **自动生成** ⚡ - 翻译文件从注释自动生成
|
|
935
|
+
6. **类型安全** 🔒 - TypeScript 提供完整的类型检查
|
|
936
|
+
7. **易于维护** 🛠️ - 修改翻译只需修改一处
|
|
937
|
+
8. **IDE 友好** 💻 - 完整的自动补全和类型提示
|
|
938
|
+
|
|
939
|
+
**记住两个核心原则:**
|
|
940
|
+
|
|
941
|
+
1. **永远不要在代码中直接写文字!**
|
|
942
|
+
2. **开发者不需要记住翻译字符串,只需要知道变量名!**
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
// ❌ 永远不要这样做
|
|
946
|
+
<button>登录</button>
|
|
947
|
+
<h1>欢迎回来</h1>
|
|
948
|
+
<p>{t('common:confirm.delete')}</p> // 也不要直接写字符串
|
|
949
|
+
|
|
950
|
+
// ✅ 始终这样做
|
|
951
|
+
<button>{t(BUTTON_LOGIN)}</button>
|
|
952
|
+
<h1>{t(MESSAGE_WELCOME)}</h1>
|
|
953
|
+
<p>{t(CONFIRM_DELETE)}</p> // 使用变量,IDE 会自动补全
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
**开发流程:**
|
|
957
|
+
|
|
958
|
+
```typescript
|
|
959
|
+
// 1. 输入变量名前缀
|
|
960
|
+
t(BUTTON_
|
|
961
|
+
|
|
962
|
+
// 2. IDE 自动提示所有可用的 Key
|
|
963
|
+
// BUTTON_LOGIN
|
|
964
|
+
// BUTTON_SUBMIT
|
|
965
|
+
// BUTTON_CANCEL
|
|
966
|
+
// ...
|
|
967
|
+
|
|
968
|
+
// 3. 选择需要的 Key,完成!
|
|
969
|
+
t(BUTTON_LOGIN)
|
|
970
|
+
|
|
971
|
+
// 不需要记住 'common:button.login'
|
|
972
|
+
// 不需要关心翻译文件的结构
|
|
973
|
+
// 不需要担心拼写错误(TypeScript 会检查)
|
|
974
|
+
```
|
|
263
975
|
|
|
264
|
-
|
|
976
|
+
---
|
|
265
977
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
- 避免重复翻译键
|
|
978
|
+
**问题反馈:**
|
|
979
|
+
如果你对国际化系统有任何疑问或建议,请在团队频道中讨论或提交 Issue。
|