From 75a48c7b6e3d68a9a41f046e0abb9958890ba83c Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:48:03 +0800 Subject: [PATCH 01/12] feat: delete plugin work not use file --- packages/server/plugin-worker/CHANGELOG.md | 1387 -------------------- packages/server/plugin-worker/package.json | 78 -- 2 files changed, 1465 deletions(-) delete mode 100644 packages/server/plugin-worker/CHANGELOG.md delete mode 100644 packages/server/plugin-worker/package.json diff --git a/packages/server/plugin-worker/CHANGELOG.md b/packages/server/plugin-worker/CHANGELOG.md deleted file mode 100644 index d667ca4234e6..000000000000 --- a/packages/server/plugin-worker/CHANGELOG.md +++ /dev/null @@ -1,1387 +0,0 @@ -# @modern-js/plugin-worker - -## 2.68.1 - -### Patch Changes - -- @modern-js/prod-server@2.68.1 -- @modern-js/server-utils@2.68.1 -- @modern-js/utils@2.68.1 - -## 2.68.0 - -### Patch Changes - -- Updated dependencies [992194b] - - @modern-js/prod-server@2.68.0 - - @modern-js/server-utils@2.68.0 - - @modern-js/utils@2.68.0 - -## 2.67.11 - -### Patch Changes - -- @modern-js/prod-server@2.67.11 -- @modern-js/server-utils@2.67.11 -- @modern-js/utils@2.67.11 - -## 2.67.10 - -### Patch Changes - -- @modern-js/prod-server@2.67.10 -- @modern-js/server-utils@2.67.10 -- @modern-js/utils@2.67.10 - -## 2.67.9 - -### Patch Changes - -- @modern-js/prod-server@2.67.9 -- @modern-js/server-utils@2.67.9 -- @modern-js/utils@2.67.9 - -## 2.67.8 - -### Patch Changes - -- Updated dependencies [532bbcb] -- Updated dependencies [23c8201] - - @modern-js/prod-server@2.67.8 - - @modern-js/utils@2.67.8 - - @modern-js/server-utils@2.67.8 - -## 2.67.7 - -### Patch Changes - -- @modern-js/prod-server@2.67.7 -- @modern-js/server-utils@2.67.7 -- @modern-js/utils@2.67.7 - -## 2.67.6 - -### Patch Changes - -- @modern-js/prod-server@2.67.6 -- @modern-js/server-utils@2.67.6 -- @modern-js/utils@2.67.6 - -## 2.67.5 - -### Patch Changes - -- @modern-js/prod-server@2.67.5 -- @modern-js/server-utils@2.67.5 -- @modern-js/utils@2.67.5 - -## 2.67.4 - -### Patch Changes - -- Updated dependencies [35e9786] - - @modern-js/prod-server@2.67.4 - - @modern-js/server-utils@2.67.4 - - @modern-js/utils@2.67.4 - -## 2.67.3 - -### Patch Changes - -- @modern-js/prod-server@2.67.3 -- @modern-js/server-utils@2.67.3 -- @modern-js/utils@2.67.3 - -## 2.67.2 - -### Patch Changes - -- @modern-js/prod-server@2.67.2 -- @modern-js/server-utils@2.67.2 -- @modern-js/utils@2.67.2 - -## 2.67.1 - -### Patch Changes - -- Updated dependencies [1d96265] - - @modern-js/prod-server@2.67.1 - - @modern-js/utils@2.67.1 - - @modern-js/server-utils@2.67.1 - -## 2.67.0 - -### Patch Changes - -- @modern-js/prod-server@2.67.0 -- @modern-js/server-utils@2.67.0 -- @modern-js/utils@2.67.0 - -## 2.66.0 - -### Patch Changes - -- @modern-js/prod-server@2.66.0 -- @modern-js/server-utils@2.66.0 -- @modern-js/utils@2.66.0 - -## 2.65.5 - -### Patch Changes - -- @modern-js/prod-server@2.65.5 -- @modern-js/server-utils@2.65.5 -- @modern-js/utils@2.65.5 - -## 2.65.4 - -### Patch Changes - -- Updated dependencies [0d47cb8] - - @modern-js/utils@2.65.4 - - @modern-js/prod-server@2.65.4 - - @modern-js/server-utils@2.65.4 - -## 2.65.3 - -### Patch Changes - -- @modern-js/prod-server@2.65.3 -- @modern-js/utils@2.65.3 -- @modern-js/server-utils@2.65.3 - -## 2.65.2 - -### Patch Changes - -- Updated dependencies [1f83d96] - - @modern-js/utils@2.65.2 - - @modern-js/prod-server@2.65.2 - - @modern-js/server-utils@2.65.2 - -## 2.65.1 - -### Patch Changes - -- @modern-js/prod-server@2.65.1 -- @modern-js/server-utils@2.65.1 -- @modern-js/utils@2.65.1 - -## 2.65.0 - -### Patch Changes - -- @modern-js/prod-server@2.65.0 -- @modern-js/server-utils@2.65.0 -- @modern-js/utils@2.65.0 - -## 2.64.3 - -### Patch Changes - -- @modern-js/prod-server@2.64.3 -- @modern-js/server-utils@2.64.3 -- @modern-js/utils@2.64.3 - -## 2.64.2 - -### Patch Changes - -- Updated dependencies [4ae943d] -- Updated dependencies [c73217b] -- Updated dependencies [02ca983] - - @modern-js/prod-server@2.64.2 - - @modern-js/server-utils@2.64.2 - - @modern-js/utils@2.64.2 - -## 2.64.1 - -### Patch Changes - -- @modern-js/prod-server@2.64.1 -- @modern-js/server-utils@2.64.1 -- @modern-js/utils@2.64.1 - -## 2.64.0 - -### Patch Changes - -- @modern-js/prod-server@2.64.0 -- @modern-js/server-utils@2.64.0 -- @modern-js/utils@2.64.0 - -## 2.63.7 - -### Patch Changes - -- @modern-js/prod-server@2.63.7 -- @modern-js/server-utils@2.63.7 -- @modern-js/utils@2.63.7 - -## 2.63.6 - -### Patch Changes - -- @modern-js/prod-server@2.63.6 -- @modern-js/server-utils@2.63.6 -- @modern-js/utils@2.63.6 - -## 2.63.5 - -### Patch Changes - -- @modern-js/prod-server@2.63.5 -- @modern-js/server-utils@2.63.5 -- @modern-js/utils@2.63.5 - -## 2.63.4 - -### Patch Changes - -- @modern-js/prod-server@2.63.4 -- @modern-js/server-utils@2.63.4 -- @modern-js/utils@2.63.4 - -## 2.63.3 - -### Patch Changes - -- @modern-js/prod-server@2.63.3 -- @modern-js/server-utils@2.63.3 -- @modern-js/utils@2.63.3 - -## 2.63.2 - -### Patch Changes - -- Updated dependencies [5fc95f7] -- Updated dependencies [53e3ae0] - - @modern-js/utils@2.63.2 - - @modern-js/prod-server@2.63.2 - - @modern-js/server-utils@2.63.2 - -## 2.63.1 - -### Patch Changes - -- @modern-js/prod-server@2.63.1 -- @modern-js/server-utils@2.63.1 -- @modern-js/utils@2.63.1 - -## 2.63.0 - -### Patch Changes - -- @modern-js/prod-server@2.63.0 -- @modern-js/server-utils@2.63.0 -- @modern-js/utils@2.63.0 - -## 2.62.1 - -### Patch Changes - -- @modern-js/prod-server@2.62.1 -- @modern-js/server-utils@2.62.1 -- @modern-js/utils@2.62.1 - -## 2.62.0 - -### Patch Changes - -- @modern-js/prod-server@2.62.0 -- @modern-js/server-utils@2.62.0 -- @modern-js/utils@2.62.0 - -## 2.61.0 - -### Patch Changes - -- Updated dependencies [45230e2] - - @modern-js/utils@2.61.0 - - @modern-js/prod-server@2.61.0 - - @modern-js/server-utils@2.61.0 - -## 2.60.6 - -### Patch Changes - -- Updated dependencies [e6daf22] - - @modern-js/prod-server@2.60.6 - - @modern-js/server-utils@2.60.6 - - @modern-js/utils@2.60.6 - -## 2.60.5 - -### Patch Changes - -- @modern-js/prod-server@2.60.5 -- @modern-js/server-utils@2.60.5 -- @modern-js/utils@2.60.5 - -## 2.60.4 - -### Patch Changes - -- Updated dependencies [75ff77f] - - @modern-js/server-utils@2.60.4 - - @modern-js/prod-server@2.60.4 - - @modern-js/utils@2.60.4 - -## 2.60.3 - -### Patch Changes - -- Updated dependencies [303331c] - - @modern-js/utils@2.60.3 - - @modern-js/prod-server@2.60.3 - - @modern-js/server-utils@2.60.3 - -## 2.60.2 - -### Patch Changes - -- Updated dependencies [8a709bc] - - @modern-js/utils@2.60.2 - - @modern-js/prod-server@2.60.2 - - @modern-js/server-utils@2.60.2 - -## 2.60.1 - -### Patch Changes - -- Updated dependencies [3a973a2] - - @modern-js/server-utils@2.60.1 - - @modern-js/prod-server@2.60.1 - - @modern-js/utils@2.60.1 - -## 2.60.0 - -### Patch Changes - -- Updated dependencies [28f0101] - - @modern-js/prod-server@2.60.0 - - @modern-js/server-utils@2.60.0 - - @modern-js/utils@2.60.0 - -## 2.59.0 - -### Patch Changes - -- @modern-js/prod-server@2.59.0 -- @modern-js/utils@2.59.0 -- @modern-js/server-utils@2.59.0 - -## 2.58.3 - -### Patch Changes - -- @modern-js/prod-server@2.58.3 -- @modern-js/server-utils@2.58.3 -- @modern-js/utils@2.58.3 - -## 2.58.2 - -### Patch Changes - -- Updated dependencies [a1a9373] - - @modern-js/utils@2.58.2 - - @modern-js/prod-server@2.58.2 - - @modern-js/server-utils@2.58.2 - -## 2.58.1 - -### Patch Changes - -- @modern-js/prod-server@2.58.1 -- @modern-js/utils@2.58.1 -- @modern-js/server-utils@2.58.1 - -## 2.58.0 - -### Patch Changes - -- @modern-js/prod-server@2.58.0 -- @modern-js/server-utils@2.58.0 -- @modern-js/utils@2.58.0 - -## 2.57.1 - -### Patch Changes - -- @modern-js/prod-server@2.57.1 -- @modern-js/server-utils@2.57.1 -- @modern-js/utils@2.57.1 - -## 2.57.0 - -### Patch Changes - -- Updated dependencies [2515b00] -- Updated dependencies [0e906a1] -- Updated dependencies [604ad3a] - - @modern-js/utils@2.57.0 - - @modern-js/prod-server@2.57.0 - - @modern-js/server-utils@2.57.0 - -## 2.56.2 - -### Patch Changes - -- @modern-js/prod-server@2.56.2 -- @modern-js/server-utils@2.56.2 -- @modern-js/utils@2.56.2 - -## 2.56.1 - -### Patch Changes - -- @modern-js/prod-server@2.56.1 -- @modern-js/server-utils@2.56.1 -- @modern-js/utils@2.56.1 - -## 2.56.0 - -### Patch Changes - -- Updated dependencies [bedbbb3] - - @modern-js/prod-server@2.56.0 - - @modern-js/utils@2.56.0 - - @modern-js/server-utils@2.56.0 - -## 2.55.0 - -### Patch Changes - -- Updated dependencies [35cddd7] -- Updated dependencies [bbcf55a] - - @modern-js/prod-server@2.55.0 - - @modern-js/utils@2.55.0 - - @modern-js/server-utils@2.55.0 - -## 2.54.6 - -### Patch Changes - -- @modern-js/prod-server@2.54.6 -- @modern-js/server-utils@2.54.6 -- @modern-js/utils@2.54.6 - -## 2.54.5 - -### Patch Changes - -- Updated dependencies [5525a23] - - @modern-js/prod-server@2.54.5 - - @modern-js/server-utils@2.54.5 - - @modern-js/utils@2.54.5 - -## 2.54.4 - -### Patch Changes - -- @modern-js/prod-server@2.54.4 -- @modern-js/server-utils@2.54.4 -- @modern-js/utils@2.54.4 - -## 2.54.3 - -### Patch Changes - -- Updated dependencies [b50d7ec] -- Updated dependencies [c5644c9] - - @modern-js/prod-server@2.54.3 - - @modern-js/server-utils@2.54.3 - - @modern-js/utils@2.54.3 - -## 2.54.2 - -### Patch Changes - -- @modern-js/prod-server@2.54.2 -- @modern-js/server-utils@2.54.2 -- @modern-js/utils@2.54.2 - -## 2.54.1 - -### Patch Changes - -- @modern-js/prod-server@2.54.1 -- @modern-js/server-utils@2.54.1 -- @modern-js/utils@2.54.1 - -## 2.54.0 - -### Patch Changes - -- Updated dependencies [15a090c] -- Updated dependencies [a8d8f0c] -- Updated dependencies [09798ac] - - @modern-js/utils@2.54.0 - - @modern-js/prod-server@2.54.0 - - @modern-js/server-utils@2.54.0 - -## 2.53.0 - -### Patch Changes - -- @modern-js/prod-server@2.53.0 -- @modern-js/server-utils@2.53.0 -- @modern-js/utils@2.53.0 - -## 2.52.0 - -### Patch Changes - -- @modern-js/prod-server@2.52.0 -- @modern-js/server-utils@2.52.0 -- @modern-js/utils@2.52.0 - -## 2.51.0 - -### Patch Changes - -- @modern-js/prod-server@2.51.0 -- @modern-js/server-utils@2.51.0 -- @modern-js/utils@2.51.0 - -## 2.50.0 - -### Patch Changes - -- @modern-js/prod-server@2.50.0 -- @modern-js/server-utils@2.50.0 -- @modern-js/utils@2.50.0 - -## 2.49.4 - -### Patch Changes - -- Updated dependencies [6e12e9f] - - @modern-js/prod-server@2.49.4 - - @modern-js/server-utils@2.49.4 - - @modern-js/utils@2.49.4 - -## 2.49.3 - -### Patch Changes - -- @modern-js/prod-server@2.49.3 -- @modern-js/server-utils@2.49.3 -- @modern-js/utils@2.49.3 - -## 2.49.2 - -### Patch Changes - -- @modern-js/prod-server@2.49.2 -- @modern-js/server-utils@2.49.2 -- @modern-js/utils@2.49.2 - -## 2.49.1 - -### Patch Changes - -- Updated dependencies [0766ae2] - - @modern-js/prod-server@2.49.1 - - @modern-js/server-utils@2.49.1 - - @modern-js/utils@2.49.1 - -## 2.49.0 - -### Patch Changes - -- Updated dependencies [fa7949a] -- Updated dependencies [e8c8c5d] -- Updated dependencies [805e021] - - @modern-js/prod-server@2.49.0 - - @modern-js/utils@2.49.0 - - @modern-js/server-utils@2.49.0 - -## 2.48.6 - -### Patch Changes - -- @modern-js/prod-server@2.48.6 -- @modern-js/server-utils@2.48.6 -- @modern-js/utils@2.48.6 - -## 2.48.5 - -### Patch Changes - -- Updated dependencies [4ca9f4c] - - @modern-js/utils@2.48.5 - - @modern-js/prod-server@2.48.5 - - @modern-js/server-utils@2.48.5 - -## 2.48.4 - -### Patch Changes - -- Updated dependencies [7d2d433] - - @modern-js/utils@2.48.4 - - @modern-js/prod-server@2.48.4 - - @modern-js/server-utils@2.48.4 - -## 2.48.3 - -### Patch Changes - -- @modern-js/prod-server@2.48.3 -- @modern-js/server-utils@2.48.3 -- @modern-js/utils@2.48.3 - -## 2.48.2 - -### Patch Changes - -- @modern-js/prod-server@2.48.2 -- @modern-js/server-utils@2.48.2 -- @modern-js/utils@2.48.2 - -## 2.48.1 - -### Patch Changes - -- Updated dependencies [8942b90] -- Updated dependencies [24b2ec1] -- Updated dependencies [ce426f7] -- Updated dependencies [74749ae] - - @modern-js/utils@2.48.1 - - @modern-js/prod-server@2.48.1 - - @modern-js/server-utils@2.48.1 - -## 2.48.0 - -### Patch Changes - -- Updated dependencies [0b44ddb] -- Updated dependencies [c323a23] - - @modern-js/prod-server@2.48.0 - - @modern-js/utils@2.48.0 - - @modern-js/server-utils@2.48.0 - -## 2.47.1 - -### Patch Changes - -- @modern-js/prod-server@2.47.1 -- @modern-js/server-utils@2.47.1 -- @modern-js/utils@2.47.1 - -## 2.47.0 - -### Patch Changes - -- Updated dependencies [b68c12a] -- Updated dependencies [a5386ab] -- Updated dependencies [a9a3626] -- Updated dependencies [01b75e6] - - @modern-js/prod-server@2.47.0 - - @modern-js/utils@2.47.0 - - @modern-js/server-utils@2.47.0 - -## 2.46.1 - -### Patch Changes - -- @modern-js/prod-server@2.46.1 -- @modern-js/server-utils@2.46.1 -- @modern-js/utils@2.46.1 - -## 2.46.0 - -### Patch Changes - -- Updated dependencies [091c7c2] -- Updated dependencies [d833015] -- Updated dependencies [46e6d56] -- Updated dependencies [494b290] -- Updated dependencies [4699e22] - - @modern-js/prod-server@2.46.0 - - @modern-js/server-utils@2.46.0 - - @modern-js/utils@2.46.0 - -## 2.45.0 - -### Patch Changes - -- Updated dependencies [f50ad3e] - - @modern-js/prod-server@2.45.0 - - @modern-js/server-utils@2.45.0 - - @modern-js/utils@2.45.0 - -## 2.44.0 - -### Patch Changes - -- Updated dependencies [0ed968c] -- Updated dependencies [56d7f9a] - - @modern-js/prod-server@2.44.0 - - @modern-js/utils@2.44.0 - - @modern-js/server-utils@2.44.0 - -## 2.43.0 - -### Patch Changes - -- Updated dependencies [d959200] - - @modern-js/prod-server@2.43.0 - - @modern-js/utils@2.43.0 - - @modern-js/server-utils@2.43.0 - -## 2.42.2 - -### Patch Changes - -- Updated dependencies [07c56c0] - - @modern-js/server-utils@2.42.2 - - @modern-js/prod-server@2.42.2 - - @modern-js/utils@2.42.2 - -## 2.42.1 - -### Patch Changes - -- @modern-js/prod-server@2.42.1 -- @modern-js/server-utils@2.42.1 -- @modern-js/utils@2.42.1 - -## 2.42.0 - -### Patch Changes - -- Updated dependencies [8c2efe1] -- Updated dependencies [8793e64] - - @modern-js/prod-server@2.42.0 - - @modern-js/server-utils@2.42.0 - - @modern-js/utils@2.42.0 - -## 2.41.0 - -### Patch Changes - -- c4d396a: chore(swc): bump swc and helpers - chore(swc): 升级 swc 以及 helpers -- Updated dependencies [2555586] -- Updated dependencies [c4d396a] - - @modern-js/prod-server@2.41.0 - - @modern-js/utils@2.41.0 - - @modern-js/server-utils@2.41.0 - -## 2.40.0 - -### Minor Changes - -- 95f15d2: chore: remove ajv schema verification of configuration - chore: 移除 ajv 对项目配置的校验 - -### Patch Changes - -- Updated dependencies [95f15d2] -- Updated dependencies [c960bcb] -- Updated dependencies [a68a2e9] - - @modern-js/utils@2.40.0 - - @modern-js/prod-server@2.40.0 - - @modern-js/server-utils@2.40.0 - -## 2.39.2 - -### Patch Changes - -- @modern-js/prod-server@2.39.2 -- @modern-js/server-utils@2.39.2 -- @modern-js/utils@2.39.2 - -## 2.39.1 - -### Patch Changes - -- @modern-js/prod-server@2.39.1 -- @modern-js/server-utils@2.39.1 -- @modern-js/utils@2.39.1 - -## 2.39.0 - -### Patch Changes - -- @modern-js/prod-server@2.39.0 -- @modern-js/server-utils@2.39.0 -- @modern-js/utils@2.39.0 - -## 2.38.0 - -### Patch Changes - -- Updated dependencies [3304d33] -- Updated dependencies [6d8ef7d] - - @modern-js/server-utils@2.38.0 - - @modern-js/prod-server@2.38.0 - - @modern-js/utils@2.38.0 - -## 2.37.2 - -### Patch Changes - -- @modern-js/prod-server@2.37.2 -- @modern-js/server-utils@2.37.2 -- @modern-js/utils@2.37.2 - -## 2.37.1 - -### Patch Changes - -- @modern-js/prod-server@2.37.1 -- @modern-js/server-utils@2.37.1 -- @modern-js/utils@2.37.1 - -## 2.37.0 - -### Patch Changes - -- Updated dependencies [383b636] -- Updated dependencies [ce0a14e] -- Updated dependencies [708f248] - - @modern-js/utils@2.37.0 - - @modern-js/server-utils@2.37.0 - - @modern-js/prod-server@2.37.0 - -## 2.36.0 - -### Patch Changes - -- Updated dependencies [3473bee] -- Updated dependencies [b98f8aa] -- Updated dependencies [eb602d2] - - @modern-js/utils@2.36.0 - - @modern-js/prod-server@2.36.0 - - @modern-js/server-utils@2.36.0 - -## 2.35.1 - -### Patch Changes - -- Updated dependencies [ea3fe18] -- Updated dependencies [bb97082] -- Updated dependencies [9dd3151] -- Updated dependencies [0db5680] -- Updated dependencies [411cea2] -- Updated dependencies [4980480] -- Updated dependencies [6a1d46e] - - @modern-js/utils@2.35.1 - - @modern-js/prod-server@2.35.1 - - @modern-js/server-utils@2.35.1 - -## 2.35.0 - -### Patch Changes - -- Updated dependencies [15b834f] - - @modern-js/utils@2.35.0 - - @modern-js/prod-server@2.35.0 - - @modern-js/server-utils@2.35.0 - -## 2.34.0 - -### Patch Changes - -- Updated dependencies [f851fa9] -- Updated dependencies [7d70738] -- Updated dependencies [5240e5d] -- Updated dependencies [a77b82a] -- Updated dependencies [c8b448b] - - @modern-js/prod-server@2.34.0 - - @modern-js/utils@2.34.0 - - @modern-js/server-utils@2.34.0 - -## 2.33.1 - -### Patch Changes - -- @modern-js/prod-server@2.33.1 -- @modern-js/server-utils@2.33.1 -- @modern-js/utils@2.33.1 - -## 2.33.0 - -### Patch Changes - -- Updated dependencies [fd82137] -- Updated dependencies [1042583] -- Updated dependencies [bc1f8da] -- Updated dependencies [3ba1682] - - @modern-js/utils@2.33.0 - - @modern-js/prod-server@2.33.0 - - @modern-js/server-utils@2.33.0 - -## 2.32.1 - -### Patch Changes - -- @modern-js/prod-server@2.32.1 -- @modern-js/utils@2.32.1 -- @modern-js/server-utils@2.32.1 - -## 2.32.0 - -### Patch Changes - -- 6076166: fix: packaging errors found by publint - - fix: 修复 publint 检测到的 packaging 问题 - -- Updated dependencies [e6c7d33] -- Updated dependencies [e5a3fb4] -- Updated dependencies [6076166] -- Updated dependencies [5f7c714] -- Updated dependencies [a030aff] -- Updated dependencies [6d73519] -- Updated dependencies [79658a0] -- Updated dependencies [3c91100] -- Updated dependencies [2447d64] -- Updated dependencies [5255eba] - - @modern-js/prod-server@2.32.0 - - @modern-js/utils@2.32.0 - - @modern-js/server-utils@2.32.0 - -## 2.31.2 - -### Patch Changes - -- Updated dependencies [15d30abdc66] - - @modern-js/utils@2.31.2 - - @modern-js/prod-server@2.31.2 - - @modern-js/server-utils@2.31.2 - -## 2.31.1 - -### Patch Changes - -- @modern-js/prod-server@2.31.1 -- @modern-js/server-utils@2.31.1 -- @modern-js/utils@2.31.1 - -## 2.31.0 - -### Patch Changes - -- Updated dependencies [56eaa0b] -- Updated dependencies [d9cc4d8] -- Updated dependencies [1882366] - - @modern-js/prod-server@2.31.0 - - @modern-js/utils@2.31.0 - - @modern-js/server-utils@2.31.0 - -## 2.30.0 - -### Patch Changes - -- Updated dependencies [9f21f28] -- Updated dependencies [a5ee81a] -- Updated dependencies [883692c] -- Updated dependencies [cc5f49e] -- Updated dependencies [b6ab299] - - @modern-js/prod-server@2.30.0 - - @modern-js/server-utils@2.30.0 - - @modern-js/utils@2.30.0 - -## 2.29.0 - -### Patch Changes - -- Updated dependencies [e6b5355] -- Updated dependencies [16e5195] -- Updated dependencies [93db783] -- Updated dependencies [cba7675] -- Updated dependencies [76ace5d] -- Updated dependencies [99052ea] -- Updated dependencies [1d71d2e] - - @modern-js/utils@2.29.0 - - @modern-js/prod-server@2.29.0 - - @modern-js/server-utils@2.29.0 - -## 2.28.0 - -### Patch Changes - -- Updated dependencies [6eae1e7] -- Updated dependencies [00b58a7] - - @modern-js/prod-server@2.28.0 - - @modern-js/utils@2.28.0 - - @modern-js/server-utils@2.28.0 - -## 2.27.0 - -### Patch Changes - -- Updated dependencies [91d14b8] -- Updated dependencies [6d7104d] - - @modern-js/utils@2.27.0 - - @modern-js/prod-server@2.27.0 - - @modern-js/server-utils@2.27.0 - -## 2.26.0 - -### Patch Changes - -- Updated dependencies [1fb5804] - - @modern-js/server-utils@2.26.0 - - @modern-js/prod-server@2.26.0 - - @modern-js/utils@2.26.0 - -## 2.25.2 - -### Patch Changes - -- Updated dependencies [63d8247] -- Updated dependencies [6651684] -- Updated dependencies [272646c] -- Updated dependencies [358ed24] - - @modern-js/utils@2.25.2 - - @modern-js/prod-server@2.25.2 - - @modern-js/server-utils@2.25.2 - -## 2.25.1 - -### Patch Changes - -- Updated dependencies [9f78d0c] - - @modern-js/utils@2.25.1 - - @modern-js/prod-server@2.25.1 - - @modern-js/server-utils@2.25.1 - -## 2.25.0 - -### Patch Changes - -- 4c4c0ad: feat: add named exports for all CLI plugins - - feat: 为各个 CLI 插件添加 named 导出 - -- Updated dependencies [2491875] -- Updated dependencies [5732c6a] - - @modern-js/prod-server@2.25.0 - - @modern-js/utils@2.25.0 - - @modern-js/server-utils@2.25.0 - -## 2.24.0 - -### Patch Changes - -- Updated dependencies [c882fbd] -- Updated dependencies [36f5bdf] -- Updated dependencies [4a82c3b] - - @modern-js/utils@2.24.0 - - @modern-js/server-utils@2.24.0 - - @modern-js/prod-server@2.24.0 - -## 2.23.1 - -### Patch Changes - -- Updated dependencies [f08bbfc] -- Updated dependencies [a6b313a] -- Updated dependencies [8f2cab0] - - @modern-js/utils@2.23.1 - - @modern-js/prod-server@2.23.1 - - @modern-js/server-utils@2.23.1 - -## 2.23.0 - -### Patch Changes - -- 7e6fb5f: chore: publishConfig add provenance config - - chore: publishConfig 增加 provenance 配置 - -- c3216b5: chore: split the scheme into the plugin - - chore: 拆分 scheme 到插件内部 - -- Updated dependencies [7e6fb5f] -- Updated dependencies [a7a7ad7] -- Updated dependencies [6dec7c2] -- Updated dependencies [c3216b5] - - @modern-js/prod-server@2.23.0 - - @modern-js/utils@2.23.0 - - @modern-js/server-utils@2.23.0 - -## 2.22.1 - -### Patch Changes - -- Updated dependencies [e2848a2] -- Updated dependencies [4be1da5] -- Updated dependencies [d4045ed] -- Updated dependencies [1f02cd2] - - @modern-js/utils@2.22.1 - - @modern-js/server-utils@2.22.1 - - @modern-js/prod-server@2.22.1 - -## 2.22.0 - -### Minor Changes - -- cb9e1ec: feat: worker ssr support server hooks - feat: worker ssr 支持 server hooks - -### Patch Changes - -- Updated dependencies [3d48836] -- Updated dependencies [5050e8e] -- Updated dependencies [4991c8a] -- Updated dependencies [cb9e1ec] -- Updated dependencies [ea961e7] - - @modern-js/utils@2.22.0 - - @modern-js/prod-server@2.22.0 - - @modern-js/server-utils@2.22.0 - -## 2.21.1 - -### Patch Changes - -- Updated dependencies [78e3ac8] -- Updated dependencies [2728724] - - @modern-js/prod-server@2.21.1 - - @modern-js/server-utils@2.21.1 - - @modern-js/utils@2.21.1 - -## 2.21.0 - -### Patch Changes - -- 26dcf3a: chore: bump typescript to v5 in devDependencies - - chore: 升级 devDependencies 中的 typescript 版本到 v5 - -- Updated dependencies [cfc5bda] -- Updated dependencies [e81eeaf] -- Updated dependencies [26dcf3a] -- Updated dependencies [056627f] -- Updated dependencies [0fc15ca] -- Updated dependencies [43b4e83] -- Updated dependencies [ad78387] - - @modern-js/prod-server@2.21.0 - - @modern-js/utils@2.21.0 - - @modern-js/server-utils@2.21.0 - -## 2.20.0 - -### Patch Changes - -- 6b9d90a: chore: remove @babel/runtime. add @swc/helper and enable `externalHelper` config. - chore: 移除 @babel/runtime 依赖. 增加 @swc/helpers 依赖并且开启 `externalHelpers` 配置 -- 4425fd6: fix: worker ssr context lack some fields & worker ssr must have routerManifest.json - fix: worker ssr 上下文缺少一些字段,worker ssr 必须有 routerManifest.json 文件 -- Updated dependencies [3c4e0a5] -- Updated dependencies [6b9d90a] -- Updated dependencies [4425fd6] - - @modern-js/utils@2.20.0 - - @modern-js/prod-server@2.20.0 - - @modern-js/server-utils@2.20.0 - -## 2.19.1 - -### Patch Changes - -- @modern-js/prod-server@2.19.1 -- @modern-js/server-utils@2.19.1 -- @modern-js/utils@2.19.1 - -## 2.19.0 - -### Patch Changes - -- Updated dependencies [1134fe2] - - @modern-js/utils@2.19.0 - - @modern-js/prod-server@2.19.0 - - @modern-js/server-utils@2.19.0 - -## 2.18.1 - -### Patch Changes - -- @modern-js/prod-server@2.18.1 -- @modern-js/server-utils@2.18.1 -- @modern-js/utils@2.18.1 - -## 2.18.0 - -### Patch Changes - -- @modern-js/prod-server@2.18.0 -- @modern-js/server-utils@2.18.0 -- @modern-js/utils@2.18.0 - -## 2.17.1 - -### Patch Changes - -- @modern-js/prod-server@2.17.1 -- @modern-js/server-utils@2.17.1 -- @modern-js/utils@2.17.1 - -## 2.17.0 - -### Patch Changes - -- @modern-js/prod-server@2.17.0 -- @modern-js/server-utils@2.17.0 -- @modern-js/utils@2.17.0 - -## 2.16.0 - -### Patch Changes - -- 4e876ab: chore: package.json include the monorepo-relative directory - - chore: 在 package.json 中声明 monorepo 的子路径 - -- Updated dependencies [5954330] -- Updated dependencies [7596520] -- Updated dependencies [4e876ab] - - @modern-js/utils@2.16.0 - - @modern-js/prod-server@2.16.0 - - @modern-js/server-utils@2.16.0 - -## 2.15.0 - -### Patch Changes - -- @modern-js/prod-server@2.15.0 -- @modern-js/server-utils@2.15.0 -- @modern-js/utils@2.15.0 - -## 2.14.0 - -### Patch Changes - -- 9321bef: feat: adjust server.worker config to deploy.worker.ssr - - feat: 调整 server.worker 为 deploy.worker.ssr - -- Updated dependencies [4779152] -- Updated dependencies [8a3c693] -- Updated dependencies [9321bef] -- Updated dependencies [9b45c58] -- Updated dependencies [52d0cb1] -- Updated dependencies [60a81d0] -- Updated dependencies [dacef96] -- Updated dependencies [16399fd] - - @modern-js/utils@2.14.0 - - @modern-js/server-utils@2.14.0 - - @modern-js/prod-server@2.14.0 - -## 2.13.4 - -### Patch Changes - -- @modern-js/prod-server@2.13.4 -- @modern-js/server-utils@2.13.4 -- @modern-js/utils@2.13.4 - -## 2.13.3 - -### Patch Changes - -- @modern-js/prod-server@2.13.3 -- @modern-js/server-utils@2.13.3 -- @modern-js/utils@2.13.3 - -## 2.13.2 - -### Patch Changes - -- @modern-js/prod-server@2.13.2 -- @modern-js/server-utils@2.13.2 -- @modern-js/utils@2.13.2 - -## 2.13.1 - -### Patch Changes - -- @modern-js/prod-server@2.13.1 -- @modern-js/server-utils@2.13.1 -- @modern-js/utils@2.13.1 - -## 2.13.0 - -### Patch Changes - -- @modern-js/prod-server@2.13.0 -- @modern-js/server-utils@2.13.0 -- @modern-js/utils@2.13.0 - -## 2.12.0 - -### Patch Changes - -- Updated dependencies [c2ca6c8] -- Updated dependencies [7980842] -- Updated dependencies [6d86e34] -- Updated dependencies [05493a7] - - @modern-js/utils@2.12.0 - - @modern-js/prod-server@2.12.0 - - @modern-js/server-utils@2.12.0 - -## 2.11.0 - -### Patch Changes - -- Updated dependencies [cfb058f] -- Updated dependencies [0bd018b] -- Updated dependencies [5d624fd] -- Updated dependencies [e2466a1] -- Updated dependencies [02bb383] -- Updated dependencies [381a3b9] -- Updated dependencies [7a60f10] -- Updated dependencies [274b2e5] -- Updated dependencies [b9e1c54] - - @modern-js/utils@2.11.0 - - @modern-js/prod-server@2.11.0 - - @modern-js/server-utils@2.11.0 - -## 2.10.0 - -### Patch Changes - -- 0da32d0: chore: upgrade jest and puppeteer - chore: 升级 jest 和 puppeteer 到 latest -- 0d9962b: fix: add types field in package.json - fix: 添加 package.json 中的 types 字段 -- Updated dependencies [d8bbf28] -- Updated dependencies [3e0bd50] -- Updated dependencies [92d247f] -- Updated dependencies [0da32d0] -- Updated dependencies [0d9962b] -- Updated dependencies [fbefa7e] -- Updated dependencies [4d54233] -- Updated dependencies [6db4864] - - @modern-js/prod-server@2.10.0 - - @modern-js/utils@2.10.0 - - @modern-js/server-utils@2.10.0 - -## 2.9.0 - -### Patch Changes - -- 7035d5c22f: fix: worker router config error when enable bff - - fix: 修复当开启 BFF 时 worker 路由配置 - -- Updated dependencies [49bb8cd0ef] - - @modern-js/prod-server@2.9.0 - - @modern-js/server-utils@2.9.0 - - @modern-js/utils@2.9.0 - -## 2.8.0 - -### Patch Changes - -- Updated dependencies [1104a9f18b] -- Updated dependencies [70d82e1408] -- Updated dependencies [2c1151271d] -- Updated dependencies [1f6ca2c7fb] - - @modern-js/prod-server@2.8.0 - - @modern-js/utils@2.8.0 - - @modern-js/server-utils@2.8.0 diff --git a/packages/server/plugin-worker/package.json b/packages/server/plugin-worker/package.json deleted file mode 100644 index e74d56dfa7f4..000000000000 --- a/packages/server/plugin-worker/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "@modern-js/plugin-worker", - "description": "The meta-framework suite designed from scratch for frontend-focused modern web development.", - "homepage": "https://modernjs.dev", - "bugs": "https://github.com/web-infra-dev/modern.js/issues", - "repository": { - "type": "git", - "url": "https://github.com/web-infra-dev/modern.js", - "directory": "packages/server/plugin-worker" - }, - "license": "MIT", - "keywords": [ - "react", - "framework", - "modern", - "modern.js" - ], - "version": "2.68.1", - "types": "./src", - "jsnext:source": "./src/index.ts", - "main": "./dist/cjs/index.js", - "exports": { - ".": { - "types": "./dist/types/index.d.ts", - "jsnext:source": "./src/index.ts", - "default": "./dist/cjs/index.js" - }, - "./cli": { - "types": "./dist/types/index.d.ts", - "jsnext:source": "./src/index.ts", - "default": "./dist/cjs/index.js" - } - }, - "scripts": { - "prepublishOnly": "only-allow-pnpm", - "dev": "modern-lib build --watch", - "build": "modern-lib build", - "new": "modern-lib new", - "test": "jest --passWithNoTests" - }, - "typesVersions": { - "*": { - ".": [ - "./dist/types/index.d.ts" - ], - "cli": [ - "./dist/types/index.d.ts" - ] - } - }, - "dependencies": { - "@modern-js/prod-server": "workspace:*", - "@modern-js/server-utils": "workspace:*", - "@modern-js/utils": "workspace:*", - "@swc/helpers": "^0.5.17" - }, - "devDependencies": { - "@modern-js/app-tools": "workspace:*", - "@modern-js/types": "workspace:*", - "@scripts/build": "workspace:*", - "@scripts/jest-config": "workspace:*", - "@types/jest": "^29.5.14", - "@types/node": "^20", - "jest": "^29.7.0", - "typescript": "^5" - }, - "sideEffects": [ - "*.css", - "*.less", - "*.sass", - "*.scss" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/", - "types": "./dist/types/index.d.ts" - } -} From 17d5ab4ac9ec38035926962655cebbe1a596ba44 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:48:22 +0800 Subject: [PATCH 02/12] fix: runtime merge utils --- packages/toolkit/runtime-utils/src/merge.ts | 22 ++++++++- .../toolkit/runtime-utils/tests/merge.test.ts | 47 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/runtime-utils/src/merge.ts b/packages/toolkit/runtime-utils/src/merge.ts index be2ccab81b4d..dc717f9d753b 100644 --- a/packages/toolkit/runtime-utils/src/merge.ts +++ b/packages/toolkit/runtime-utils/src/merge.ts @@ -10,6 +10,23 @@ function isObject(obj: any): obj is PlainObject { return obj && typeof obj === 'object' && !Array.isArray(obj); } +// Helper function to detect if an object is a complex instance (like i18next) +function isComplexInstance(obj: any): boolean { + if (!isObject(obj)) return false; + + // Check for common complex instance indicators + const hasMethods = + typeof obj.init === 'function' || + typeof obj.changeLanguage === 'function' || + typeof obj.t === 'function'; + const hasInternalProps = + obj.isInitialized !== undefined || + obj.language !== undefined || + obj.store !== undefined; + + return hasMethods || hasInternalProps; +} + export function merge( target: T, ...sources: U @@ -21,7 +38,10 @@ export function merge( if (isObject(target) && isObject(source)) { for (const key in source) { - if (isObject(source[key])) { + // If source[key] is a complex instance (like i18next), don't merge it deeply + if (isComplexInstance(source[key])) { + Object.assign(target, { [key]: source[key] }); + } else if (isObject(source[key])) { if (!target[key]) { Object.assign(target, { [key]: {} }); } diff --git a/packages/toolkit/runtime-utils/tests/merge.test.ts b/packages/toolkit/runtime-utils/tests/merge.test.ts index 6cf754752304..fe18c38d3554 100644 --- a/packages/toolkit/runtime-utils/tests/merge.test.ts +++ b/packages/toolkit/runtime-utils/tests/merge.test.ts @@ -57,4 +57,51 @@ describe('merge function', () => { expect(merge({}, 1 as any, { a: 1 })).toEqual({ a: 1 }); expect(merge({}, 'b' as any, { a: 1 })).toEqual({ a: 1 }); }); + + test('should handle complex instances like i18next without deep merging', () => { + // Mock i18next-like instance + const i18nInstance = { + language: 'en', + isInitialized: true, + init: jest.fn(), + changeLanguage: jest.fn(), + t: jest.fn(), + store: { + data: { en: { translation: {} } }, + }, + }; + + const config1 = { a: 1, i18n: i18nInstance }; + const config2 = { b: 2, i18n: { language: 'zh' } }; + + const result = merge({}, config1, config2); + + // Should not deep merge the i18n instance, but replace it entirely + expect(result.i18n).toBe(config2.i18n); + expect(result.a).toBe(1); + expect(result.b).toBe(2); + }); + + test('should handle circular references in complex instances', () => { + // Create a circular reference + const circularObj = { name: 'test' }; + circularObj.self = circularObj; + + const i18nInstance = { + language: 'en', + isInitialized: true, + init: jest.fn(), + changeLanguage: jest.fn(), + circular: circularObj, + }; + + const config1 = { a: 1 }; + const config2 = { i18n: i18nInstance }; + + // This should not throw a stack overflow error + expect(() => { + const result = merge({}, config1, config2); + expect(result.i18n).toBe(i18nInstance); + }).not.toThrow(); + }); }); From 7c65d5860e3afd8062f364ec757e80c6c9dd8303 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:48:32 +0800 Subject: [PATCH 03/12] feat: i18n plugin --- packages/runtime/plugin-i18n/README.md | 32 +++ packages/runtime/plugin-i18n/modern.config.js | 5 + packages/runtime/plugin-i18n/package.json | 130 +++++++++ packages/runtime/plugin-i18n/src/cli/index.ts | 47 ++++ packages/runtime/plugin-i18n/src/index.ts | 1 + .../plugin-i18n/src/runtime/I18nLink.tsx | 83 ++++++ .../plugin-i18n/src/runtime/context.tsx | 247 ++++++++++++++++++ .../plugin-i18n/src/runtime/i18n/index.ts | 8 + .../plugin-i18n/src/runtime/i18n/instance.ts | 93 +++++++ .../plugin-i18n/src/runtime/i18n/utils.ts | 7 + .../runtime/plugin-i18n/src/runtime/index.tsx | 159 +++++++++++ .../runtime/plugin-i18n/src/runtime/utils.ts | 51 ++++ .../runtime/plugin-i18n/src/server/index.ts | 117 +++++++++ .../runtime/plugin-i18n/src/utils/config.ts | 45 ++++ packages/runtime/plugin-i18n/tsconfig.json | 12 + 15 files changed, 1037 insertions(+) create mode 100644 packages/runtime/plugin-i18n/README.md create mode 100644 packages/runtime/plugin-i18n/modern.config.js create mode 100644 packages/runtime/plugin-i18n/package.json create mode 100644 packages/runtime/plugin-i18n/src/cli/index.ts create mode 100644 packages/runtime/plugin-i18n/src/index.ts create mode 100644 packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx create mode 100644 packages/runtime/plugin-i18n/src/runtime/context.tsx create mode 100644 packages/runtime/plugin-i18n/src/runtime/i18n/index.ts create mode 100644 packages/runtime/plugin-i18n/src/runtime/i18n/instance.ts create mode 100644 packages/runtime/plugin-i18n/src/runtime/i18n/utils.ts create mode 100644 packages/runtime/plugin-i18n/src/runtime/index.tsx create mode 100644 packages/runtime/plugin-i18n/src/runtime/utils.ts create mode 100644 packages/runtime/plugin-i18n/src/server/index.ts create mode 100644 packages/runtime/plugin-i18n/src/utils/config.ts create mode 100644 packages/runtime/plugin-i18n/tsconfig.json diff --git a/packages/runtime/plugin-i18n/README.md b/packages/runtime/plugin-i18n/README.md new file mode 100644 index 000000000000..7fe4e69e608a --- /dev/null +++ b/packages/runtime/plugin-i18n/README.md @@ -0,0 +1,32 @@ +

+ Modern.js Logo +

+ +

Modern.js

+ +

+ A Progressive React Framework for modern web development. +

+ +## Getting Started + +Please follow [Quick Start](https://modernjs.dev/en/guides/get-started/quick-start) to get started with Modern.js. + +## Documentation + +- [English Documentation](https://modernjs.dev/en/) +- [中文文档](https://modernjs.dev) + +## Contributing + +Please read the [Contributing Guide](https://github.com/web-infra-dev/modern.js/blob/main/CONTRIBUTING.md). + +## License + +Modern.js is [MIT licensed](https://github.com/web-infra-dev/modern.js/blob/main/LICENSE). + +## Credist + +Thanks to: + +- [@loadable/webpack-plugin](https://github.com/gregberge/loadable-components) to create a webpack plugin prepare for loadable usage in ssr. diff --git a/packages/runtime/plugin-i18n/modern.config.js b/packages/runtime/plugin-i18n/modern.config.js new file mode 100644 index 000000000000..5c0331ad0c29 --- /dev/null +++ b/packages/runtime/plugin-i18n/modern.config.js @@ -0,0 +1,5 @@ +const { universalBuildConfig } = require('@scripts/build'); + +module.exports = { + buildConfig: universalBuildConfig, +}; diff --git a/packages/runtime/plugin-i18n/package.json b/packages/runtime/plugin-i18n/package.json new file mode 100644 index 000000000000..b6537511cbda --- /dev/null +++ b/packages/runtime/plugin-i18n/package.json @@ -0,0 +1,130 @@ +{ + "name": "@modern-js/plugin-i18n", + "description": "A Progressive React Framework for modern web development.", + "homepage": "https://modernjs.dev", + "type": "module", + "bugs": "https://github.com/web-infra-dev/modern.js/issues", + "repository": { + "type": "git", + "url": "https://github.com/web-infra-dev/modern.js", + "directory": "packages/runtime/plugin-runtime" + }, + "license": "MIT", + "keywords": [ + "react", + "framework", + "modern", + "modern.js" + ], + "version": "2.68.1", + "engines": { + "node": ">=20" + }, + "jsnext:source": "./src/index.ts", + "types": "./src/index.ts", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "react-server": "./dist/esm/react-server.js", + "jsnext:source": "./src/index.ts", + "default": "./dist/esm/index.js" + }, + "./package.json": "./package.json", + "./cli": { + "types": "./dist/types/cli/index.d.ts", + "jsnext:source": "./src/cli/index.ts", + "default": "./dist/cjs/cli/index.js" + }, + "./runtime": { + "types": "./dist/types/runtime/index.d.ts", + "jsnext:source": "./src/runtime/index.ts", + "default": "./dist/esm/runtime/index.js" + }, + "./server": { + "types": "./dist/types/server/index.d.ts", + "jsnext:source": "./src/server/index.ts", + "default": "./dist/esm/server/index.js" + }, + "./i18n": { + "types": "./dist/types/runtime/i18n/index.d.ts", + "jsnext:source": "./src/runtime/i18n/index.ts", + "default": "./dist/esm/runtime/i18n/index.js" + } + }, + "typesVersions": { + "*": { + ".": [ + "./dist/types/index.d.ts" + ], + "types": [ + "./dist/types/types.d.ts" + ], + "cli": [ + "./dist/types/cli/index.d.ts" + ], + "runtime": [ + "./dist/types/runtime/index.d.ts" + ], + "server": [ + "./dist/types/server/index.d.ts" + ], + "i18n": [ + "./dist/types/runtime/i18n/index.d.ts" + ] + } + }, + "scripts": { + "dev": "modern-lib build --watch", + "prepublishOnly": "only-allow-pnpm", + "new": "modern-lib new", + "build": "modern-lib build", + "test": "jest" + }, + "dependencies": { + "@modern-js/plugin": "workspace:*", + "@modern-js/server-runtime": "workspace:*", + "@modern-js/types": "workspace:*", + "@modern-js/utils": "workspace:*", + "@swc/helpers": "^0.5.17" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17.0.2", + "i18next": ">=25.2.1", + "react-i18next": ">=15.5.2", + "@modern-js/runtime": "workspace:^2.68.1" + }, + "peerDependenciesMeta": { + "i18next": { + "optional": true + }, + "react-i18next": { + "optional": true + } + }, + "devDependencies": { + "i18next":"25.2.1", + "react-i18next":"15.5.2", + "@modern-js/runtime": "workspace:*", + "@modern-js/app-tools": "workspace:*", + "@scripts/build": "workspace:*", + "@scripts/jest-config": "workspace:*", + "@testing-library/react": "^13.4.0", + "@types/jest": "^29.5.14", + "@types/node": "^20", + "jest": "^29.7.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "ts-jest": "^29.4.5", + "ts-node": "^10.9.2", + "typescript": "^5" + }, + "sideEffects": false, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public", + "types": "./dist/types/index" + } + } diff --git a/packages/runtime/plugin-i18n/src/cli/index.ts b/packages/runtime/plugin-i18n/src/cli/index.ts new file mode 100644 index 000000000000..7b7f48093731 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/cli/index.ts @@ -0,0 +1,47 @@ +import type { AppTools, CliPlugin } from '@modern-js/app-tools'; +import { + type LocaleDetectionOptions, + getLocaleDetectionOptions, +} from '../utils/config'; + +export interface I18nPluginOptions { + localeDetection?: LocaleDetectionOptions; +} + +export const i18nPlugin = ( + options: I18nPluginOptions = {}, +): CliPlugin => ({ + name: '@modern-js/plugin-i18n', + setup: api => { + const { localeDetection } = options; + api._internalRuntimePlugins(({ entrypoint, plugins }) => { + const localeDetectionOptions = localeDetection + ? getLocaleDetectionOptions(entrypoint.entryName, localeDetection) + : undefined; + plugins.push({ + name: 'i18n', + path: '@modern-js/plugin-i18n/runtime', + config: { + entryName: entrypoint.entryName, + localeDetection: localeDetectionOptions, + }, + }); + return { + entrypoint, + plugins, + }; + }); + + api._internalServerPlugins(({ plugins }) => { + plugins.push({ + name: '@modern-js/plugin-i18n/server', + options: { + localeDetection, + }, + }); + return { plugins }; + }); + }, +}); + +export default i18nPlugin; diff --git a/packages/runtime/plugin-i18n/src/index.ts b/packages/runtime/plugin-i18n/src/index.ts new file mode 100644 index 000000000000..bf3b7e5f57f3 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/index.ts @@ -0,0 +1 @@ +export * from './cli'; diff --git a/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx b/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx new file mode 100644 index 000000000000..caf836b26660 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx @@ -0,0 +1,83 @@ +import type React from 'react'; +import { useModernI18n } from './context'; +import { buildLocalizedUrl } from './utils'; + +export interface I18nLinkProps { + to: string; + children: React.ReactNode; + [key: string]: any; // Allow other props to be passed through +} + +/** + * I18nLink component that automatically adds language prefix to navigation links. + * This component should be used within a :lang dynamic route context. + * + * @example + * ```tsx + * // When current language is 'en' and to="/about" + * // The actual link will be "/en/about" + * About + * + * // When current language is 'zh' and to="/" + * // The actual link will be "/zh" + * Home + * ``` + */ +// Safe hook wrapper for router hooks +const useRouterHooks = () => { + try { + const { Link, useParams } = require('@modern-js/runtime/router'); + return { + Link, + params: useParams(), + hasRouter: true, + }; + } catch (error) { + return { + Link: null, + params: {}, + hasRouter: false, + }; + } +}; + +export const I18nLink: React.FC = ({ + to, + children, + ...props +}) => { + const { Link, params, hasRouter } = useRouterHooks(); + const { language, supportedLanguages } = useModernI18n(); + + // Get the current language from context (which reflects the actual current language) + // URL params might be stale after language changes, so we prioritize the context language + const currentLang = language; + + // Build the localized URL by adding language prefix + const localizedTo = buildLocalizedUrl(to, currentLang, supportedLanguages); + + // In development mode, warn if used outside of :lang route context + if (process.env.NODE_ENV === 'development' && hasRouter && !params.lang) { + console.warn( + 'I18nLink is being used outside of a :lang dynamic route context. ' + + 'This may cause unexpected behavior. Please ensure I18nLink is used within a route that has a :lang parameter.', + ); + } + + // If router is not available, render as a regular anchor tag + if (!hasRouter || !Link) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export default I18nLink; diff --git a/packages/runtime/plugin-i18n/src/runtime/context.tsx b/packages/runtime/plugin-i18n/src/runtime/context.tsx new file mode 100644 index 000000000000..e52e689eaa58 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/context.tsx @@ -0,0 +1,247 @@ +import { isBrowser } from '@modern-js/runtime'; +import { createContext, useCallback, useContext } from 'react'; +import type { FC, ReactNode } from 'react'; +import type { I18nInstance } from './i18n'; +import { buildLocalizedUrl, getEntryPath } from './utils'; + +export interface ModernI18nContextValue { + language: string; + i18nInstance: I18nInstance; + // Plugin configuration for useModernI18n hook + entryName?: string; + languages?: string[]; + enableLocaleDetection?: boolean; + // Callback to update language in context + updateLanguage?: (newLang: string) => void; +} + +const ModernI18nContext = createContext(null); + +export interface ModernI18nProviderProps { + children: ReactNode; + value: ModernI18nContextValue; +} + +export const ModernI18nProvider: FC = ({ + children, + value, +}) => { + return ( + + {children} + + ); +}; + +export interface UseModernI18nOptions { + entryName?: string; + languages?: string[]; + enableLocaleDetection?: boolean; +} + +export interface UseModernI18nReturn { + language: string; + changeLanguage: (newLang: string) => Promise; + i18nInstance: I18nInstance; + supportedLanguages: string[]; + isLanguageSupported: (lang: string) => boolean; +} + +/** + * Hook for accessing i18n functionality in Modern.js applications. + * + * This hook provides: + * - Current language from URL params or i18n context + * - changeLanguage function that updates both i18n instance and URL + * - Direct access to the i18n instance + * - List of supported languages + * - Helper function to check if a language is supported + * + * @param options - Optional configuration to override context settings + * @returns Object containing i18n functionality and utilities + * + * @example + * ```tsx + * import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; + * + * function MyComponent() { + * const { + * language, + * changeLanguage, + * i18nInstance, + * supportedLanguages, + * isLanguageSupported + * } = useModernI18n(); + * + * const handleLanguageChange = (newLang: string) => { + * if (isLanguageSupported(newLang)) { + * changeLanguage(newLang); + * } + * }; + * + * return ( + *
+ *

Current language: {language}

+ *

Supported languages: {supportedLanguages.join(', ')}

+ * {supportedLanguages.map(lang => ( + * + * ))} + *
+ * ); + * } + * ``` + */ +// Safe hook wrapper to handle cases where router context is not available +const useRouterHooks = () => { + try { + // Dynamically import router hooks to avoid issues when router context is not available + const { + useLocation, + useNavigate, + useParams, + } = require('@modern-js/runtime/router'); + return { + navigate: useNavigate(), + location: useLocation(), + params: useParams(), + hasRouter: true, + }; + } catch (error) { + // Router context not available, return fallback values + return { + navigate: null, + location: null, + params: {}, + hasRouter: false, + }; + } +}; + +export const useModernI18n = ( + options: UseModernI18nOptions = {}, +): UseModernI18nReturn => { + const context = useContext(ModernI18nContext); + if (!context) { + throw new Error('useModernI18n must be used within a ModernI18nProvider'); + } + + const { + language: contextLanguage, + i18nInstance, + entryName: contextEntryName, + languages: contextLanguages, + enableLocaleDetection: contextEnableLocaleDetection, + updateLanguage, + } = context; + + // Merge context options with passed options (passed options take precedence) + const { + entryName = contextEntryName, + languages = contextLanguages || [], + enableLocaleDetection = contextEnableLocaleDetection || false, + } = options; + + // Get router hooks safely + const { navigate, location, params, hasRouter } = useRouterHooks(); + + // Get current language from context (which reflects the actual current language) + // URL params might be stale after language changes, so we prioritize the context language + const currentLanguage = contextLanguage; + + /** + * Changes the current language and updates the URL accordingly. + * + * This function: + * 1. Updates the i18n instance language + * 2. Updates the URL by replacing the language prefix in the current path + * 3. Triggers a navigation to the new URL + * + * @param newLang - The new language code to switch to + */ + const changeLanguage = useCallback( + async (newLang: string) => { + try { + // Validate language + if (!newLang || typeof newLang !== 'string') { + throw new Error('Language must be a non-empty string'); + } + + // Update i18n instance + await i18nInstance.changeLanguage(newLang); + + // Update context language state + if (updateLanguage) { + updateLanguage(newLang); + } + + // Update URL if locale detection is enabled, we're in browser, and router is available + if ( + enableLocaleDetection && + isBrowser() && + hasRouter && + navigate && + location + ) { + const currentPath = location.pathname; + const entryPath = getEntryPath(entryName); + const relativePath = currentPath.replace(entryPath, ''); + + // Build new path with updated language + const newPath = buildLocalizedUrl(relativePath, newLang, languages); + const newUrl = entryPath + newPath + location.search + location.hash; + + // Navigate to new URL + navigate(newUrl, { replace: true }); + } else if (enableLocaleDetection && isBrowser() && !hasRouter) { + // Fallback: use window.history API when router is not available + const currentPath = window.location.pathname; + const entryPath = getEntryPath(entryName); + const relativePath = currentPath.replace(entryPath, ''); + + // Build new path with updated language + const newPath = buildLocalizedUrl(relativePath, newLang, languages); + const newUrl = + entryPath + newPath + window.location.search + window.location.hash; + + // Use history API to navigate without page reload + window.history.pushState(null, '', newUrl); + } + } catch (error) { + console.error('Failed to change language:', error); + throw error; + } + }, + [ + i18nInstance, + updateLanguage, + enableLocaleDetection, + entryName, + languages, + hasRouter, + navigate, + location, + ], + ); + + // Helper function to check if a language is supported + const isLanguageSupported = useCallback( + (lang: string) => { + return languages.includes(lang); + }, + [languages], + ); + + return { + language: currentLanguage, + changeLanguage, + i18nInstance, + supportedLanguages: languages, + isLanguageSupported, + }; +}; diff --git a/packages/runtime/plugin-i18n/src/runtime/i18n/index.ts b/packages/runtime/plugin-i18n/src/runtime/i18n/index.ts new file mode 100644 index 000000000000..cd8f0d92f32f --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/i18n/index.ts @@ -0,0 +1,8 @@ +export type { + I18nInstance, + I18nInitOptions, +} from './instance'; + +export { isI18nInstance, getI18nInstance } from './instance'; + +export { assertI18nInstance } from './utils'; diff --git a/packages/runtime/plugin-i18n/src/runtime/i18n/instance.ts b/packages/runtime/plugin-i18n/src/runtime/i18n/instance.ts new file mode 100644 index 000000000000..e01eabcd8f66 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/i18n/instance.ts @@ -0,0 +1,93 @@ +// simple i18n instance definition +export interface I18nInstance { + language: string; + isInitialized: boolean; + init: (options?: I18nInitOptions) => void | Promise; + changeLanguage: (lang: string) => void | Promise; + use: (plugin: any) => void; + createInstance: (options?: I18nInitOptions) => I18nInstance; + cloneInstance?: () => I18nInstance; // ssr need +} + +export type I18nInitOptions = { + lng?: string; + fallbackLng?: string; + supportedLngs?: string[]; + initImmediate?: boolean; +}; + +export function isI18nInstance(obj: any): obj is I18nInstance { + return ( + obj && + typeof obj === 'object' && + typeof obj.init === 'function' && + typeof obj.use === 'function' + ); +} + +async function tryImportI18next(): Promise { + try { + const i18next = await import('i18next'); + return i18next.default as unknown as I18nInstance; + } catch (error) { + return null; + } +} + +async function createI18nextInstance(): Promise { + try { + const i18next = await tryImportI18next(); + if (!i18next) { + return null; + } + // Create a new instance without auto-initialization + // Use initImmediate: false to prevent auto-init + return i18next.createInstance({ + initImmediate: false, + }) as unknown as I18nInstance; + } catch (error) { + return null; + } +} + +async function tryImportReactI18next() { + try { + const reactI18next = await import('react-i18next'); + return reactI18next; + } catch (error) { + return null; + } +} + +export async function getI18nInstance( + userInstance?: I18nInstance, +): Promise { + if (userInstance) { + if (isI18nInstance(userInstance)) { + return userInstance; + } + } + + const i18nextInstance = await createI18nextInstance(); + if (i18nextInstance) { + return i18nextInstance; + } + + throw new Error('No i18n instance found'); +} + +export async function getInitReactI18next() { + const reactI18nextModule = await tryImportReactI18next(); + if (reactI18nextModule) { + return reactI18nextModule.initReactI18next; + } + return null; +} + +export async function getI18nextProvider() { + const reactI18nextModule = await tryImportReactI18next(); + if (reactI18nextModule) { + return reactI18nextModule.I18nextProvider; + } + return null; +} diff --git a/packages/runtime/plugin-i18n/src/runtime/i18n/utils.ts b/packages/runtime/plugin-i18n/src/runtime/i18n/utils.ts new file mode 100644 index 000000000000..3a27e3219c2e --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/i18n/utils.ts @@ -0,0 +1,7 @@ +import { type I18nInstance, isI18nInstance } from './instance'; + +export function assertI18nInstance(obj: any): asserts obj is I18nInstance { + if (!isI18nInstance(obj)) { + throw new Error('Object does not implement I18nInstance interface'); + } +} diff --git a/packages/runtime/plugin-i18n/src/runtime/index.tsx b/packages/runtime/plugin-i18n/src/runtime/index.tsx new file mode 100644 index 000000000000..f631bde33a33 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/index.tsx @@ -0,0 +1,159 @@ +import { + type RuntimePlugin, + isBrowser, + useRuntimeContext, +} from '@modern-js/runtime'; +import type React from 'react'; +import { useEffect, useState } from 'react'; +import type { BaseLocaleDetectionOptions } from '../utils/config'; +import { ModernI18nProvider } from './context'; +import type { I18nInitOptions, I18nInstance } from './i18n'; +import { getI18nInstance } from './i18n'; +import { getI18nextProvider, getInitReactI18next } from './i18n/instance'; +import { getEntryPath, getLanguageFromPath } from './utils'; + +export interface I18nPluginOptions { + entryName?: string; + localeDetection?: BaseLocaleDetectionOptions; + i18nInstance?: I18nInstance; + changeLanguage?: (lang: string) => void; + initOptions?: I18nInitOptions; +} + +export const i18nPlugin = (options: I18nPluginOptions): RuntimePlugin => ({ + name: '@modern-js/plugin-i18n', + setup: api => { + const { + entryName, + i18nInstance: userI18nInstance, + changeLanguage, + initOptions: userInitOptions, + localeDetection, + } = options; + const { + enable: enableLocaleDetection, + languages = [], + fallbackLanguage = 'en', + } = localeDetection || {}; + let I18nextProvider: React.FunctionComponent | null; + + // Helper function to detect language from path + const detectLanguageFromPath = (pathname: string) => { + if (enableLocaleDetection) { + const relativePath = pathname.replace(getEntryPath(entryName), ''); + const detectedLang = getLanguageFromPath( + relativePath, + languages, + fallbackLanguage, + ); + // If no language is detected from path, use fallback language + return detectedLang || fallbackLanguage; + } + return fallbackLanguage; + }; + + api.onBeforeRender(async context => { + let i18nInstance = await getI18nInstance(userI18nInstance); + const initReactI18next = await getInitReactI18next(); + I18nextProvider = await getI18nextProvider(); + if (initReactI18next) { + i18nInstance.use(initReactI18next); + } + // Always detect language from path for consistency between SSR and client + let initialLanguage = fallbackLanguage; + if (enableLocaleDetection) { + if (isBrowser()) { + // In browser, get from window.location + initialLanguage = detectLanguageFromPath(window.location.pathname); + } else { + // In SSR, get from request context + const pathname = context.ssrContext?.request?.pathname || '/'; + initialLanguage = detectLanguageFromPath(pathname); + } + } + if (!i18nInstance.isInitialized) { + const initOptions: I18nInitOptions = { + lng: initialLanguage, + fallbackLng: fallbackLanguage, + supportedLngs: languages, + ...(userInitOptions || {}), + }; + await i18nInstance.init(initOptions); + } + if (!isBrowser() && i18nInstance.cloneInstance) { + i18nInstance = i18nInstance.cloneInstance(); + } + if (enableLocaleDetection && i18nInstance.language !== initialLanguage) { + // If instance is already initialized but language doesn't match the path, update it + await i18nInstance.changeLanguage(initialLanguage); + } + context.i18nInstance = i18nInstance; + }); + + api.wrapRoot(App => { + return props => { + const runtimeContext = useRuntimeContext(); + const i18nInstance = (runtimeContext as any).i18nInstance; + const [lang, setLang] = useState(i18nInstance.language); + + if (!isBrowser) { + (i18nInstance as any).translator.language = i18nInstance.language; + } + + // Get pathname from appropriate source based on environment + const getCurrentPathname = () => { + if (isBrowser()) { + return window.location.pathname; + } else { + // In SSR, get pathname from request context + return runtimeContext.request?.pathname || '/'; + } + }; + + // Initialize language from URL on mount (only when localeDetection is enabled) + useEffect(() => { + if (enableLocaleDetection) { + const currentPathname = getCurrentPathname(); + const currentLang = detectLanguageFromPath(currentPathname); + if (currentLang !== lang) { + setLang(currentLang); + // Update i18n instance language + i18nInstance.changeLanguage(currentLang); + } + } + }, []); + + // Context value contains language, i18nInstance, and plugin configuration + // changeLanguage is now implemented in the useModernI18n hook + const contextValue = { + language: lang, + i18nInstance, + entryName, + languages, + enableLocaleDetection, + updateLanguage: setLang, + }; + + if (I18nextProvider) { + return ( + + + + + + ); + } + + return ( + + + + ); + }; + }); + }, +}); + +export { useModernI18n } from './context'; +export { I18nLink } from './I18nLink'; +export default i18nPlugin; diff --git a/packages/runtime/plugin-i18n/src/runtime/utils.ts b/packages/runtime/plugin-i18n/src/runtime/utils.ts new file mode 100644 index 000000000000..84738c5ebff5 --- /dev/null +++ b/packages/runtime/plugin-i18n/src/runtime/utils.ts @@ -0,0 +1,51 @@ +import { MAIN_ENTRY_NAME } from '@modern-js/utils/universal/constants'; + +export const getEntryPath = (entryName: string = MAIN_ENTRY_NAME) => { + return entryName === MAIN_ENTRY_NAME ? '' : `/${entryName}`; +}; +/** + * Helper function to get language from current pathname + * @param pathname - The current pathname + * @param languages - Array of supported languages + * @param fallbackLanguage - Fallback language when no language is detected + * @returns The detected language or fallback language + */ +export const getLanguageFromPath = ( + pathname: string, + languages: string[], + fallbackLanguage: string, +): string => { + const segments = pathname.split('/').filter(Boolean); + const firstSegment = segments[0]; + + if (languages.includes(firstSegment)) { + return firstSegment; + } + + return fallbackLanguage; +}; + +/** + * Helper function to build localized URL + * @param pathname - The current pathname + * @param language - The target language + * @param languages - Array of supported languages + * @returns The localized URL path + */ +export const buildLocalizedUrl = ( + pathname: string, + language: string, + languages: string[], +): string => { + const segments = pathname.split('/').filter(Boolean); + + if (segments.length > 0 && languages.includes(segments[0])) { + // Replace existing language prefix + segments[0] = language; + } else { + // Add language prefix + segments.unshift(language); + } + + return `/${segments.join('/')}`; +}; diff --git a/packages/runtime/plugin-i18n/src/server/index.ts b/packages/runtime/plugin-i18n/src/server/index.ts new file mode 100644 index 000000000000..3a9df323142f --- /dev/null +++ b/packages/runtime/plugin-i18n/src/server/index.ts @@ -0,0 +1,117 @@ +import type { Context, Next, ServerPlugin } from '@modern-js/server-runtime'; +import { + type LocaleDetectionOptions, + getLocaleDetectionOptions, +} from '../utils/config.js'; + +export interface I18nPluginOptions { + localeDetection: LocaleDetectionOptions; +} + +const getLanguageFromPath = ( + req: any, + urlPath: string, + languages: string[], +): string | null => { + const url = new URL(req.url, `http://${req.headers.host}`); + const pathname = url.pathname; + + // 移除 urlPath 前缀,获取剩余路径 + // urlPath 格式为 /lang/*,需要移除 /lang 部分 + const basePath = urlPath.replace('/*', ''); + const remainingPath = pathname.startsWith(basePath) + ? pathname.slice(basePath.length) + : pathname; + + const segments = remainingPath.split('/').filter(Boolean); + const firstSegment = segments[0]; + + if (languages.includes(firstSegment)) { + return firstSegment; + } + + return null; +}; + +const buildLocalizedUrl = ( + req: any, + urlPath: string, + language: string, + languages: string[], +): string => { + const url = new URL(req.url, `http://${req.headers.host}`); + const pathname = url.pathname; + + // 移除 urlPath 前缀,获取剩余路径 + const basePath = urlPath.replace('/*', ''); + const remainingPath = pathname.startsWith(basePath) + ? pathname.slice(basePath.length) + : pathname; + + const segments = remainingPath.split('/').filter(Boolean); + + if (segments.length > 0 && languages.includes(segments[0])) { + // 替换现有的语言前缀 + segments[0] = language; + } else { + // 如果路径不以语言开头,添加语言前缀 + segments.unshift(language); + } + + const newPathname = `/${segments.join('/')}`; + // 处理根路径的情况,避免出现 //en 这样的双斜杠 + const localizedUrl = + basePath === '/' + ? newPathname + url.search + : basePath + newPathname + url.search; + + return localizedUrl; +}; + +export const i18nServerPlugin = (options: I18nPluginOptions): ServerPlugin => ({ + name: '@modern-js/plugin-i18n/server', + setup: api => { + api.onPrepare(() => { + const { middlewares, routes } = api.getServerContext(); + routes.map(route => { + const { entryName } = route; + if (!entryName) { + return; + } + if (!options.localeDetection) { + return; + } + const { + enable, + languages = [], + fallbackLanguage = 'en', + } = getLocaleDetectionOptions(entryName, options.localeDetection); + const originUrlPath = route.urlPath; + const urlPath = originUrlPath.endsWith('/') + ? `${originUrlPath}*` + : `${originUrlPath}/*`; + if (enable) { + middlewares.push({ + name: 'i18n-server-middleware', + path: urlPath, + handler: async (c: Context, next: Next) => { + const language = getLanguageFromPath(c.req, urlPath, languages); + if (!language) { + const localizedUrl = buildLocalizedUrl( + c.req, + originUrlPath, + fallbackLanguage, + languages, + ); + return c.redirect(localizedUrl); + } + await next(); + }, + }); + } + }); + }); + }, +}); + +export default i18nServerPlugin; diff --git a/packages/runtime/plugin-i18n/src/utils/config.ts b/packages/runtime/plugin-i18n/src/utils/config.ts new file mode 100644 index 000000000000..9b0be023c65d --- /dev/null +++ b/packages/runtime/plugin-i18n/src/utils/config.ts @@ -0,0 +1,45 @@ +/** + * Base locale detection configuration for a single entry + */ +export interface BaseLocaleDetectionOptions { + /** Whether locale detection is enabled for this entry */ + enable?: boolean; + /** List of supported languages */ + languages?: string[]; + /** Fallback language when detection fails */ + fallbackLanguage?: string; +} + +/** + * Locale detection configuration that supports both global and per-entry settings + */ +export interface LocaleDetectionOptions extends BaseLocaleDetectionOptions { + /** Per-entry locale detection configurations */ + localeDetectionByEntry?: Record; +} + +/** + * Gets the locale detection options for a specific entry, falling back to global config + * @param entryName - The name of the entry to get options for + * @param localeDetection - The global locale detection configuration + * @returns The resolved locale detection options for the entry + */ +export const getLocaleDetectionOptions = ( + entryName: string, + localeDetection: BaseLocaleDetectionOptions, +): BaseLocaleDetectionOptions => { + // Type guard to check if the config has localeDetectionByEntry + const hasEntryConfig = ( + config: BaseLocaleDetectionOptions, + ): config is LocaleDetectionOptions => + config && + typeof config === 'object' && + (config as any).localeDetectionByEntry !== undefined; + + if (hasEntryConfig(localeDetection)) { + const { localeDetectionByEntry, ...globalConfig } = localeDetection; + return localeDetectionByEntry?.[entryName] || globalConfig; + } + + return localeDetection; +}; diff --git a/packages/runtime/plugin-i18n/tsconfig.json b/packages/runtime/plugin-i18n/tsconfig.json new file mode 100644 index 000000000000..4da83a44292c --- /dev/null +++ b/packages/runtime/plugin-i18n/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "lib": ["DOM", "ESNext", "dom.iterable"], + "jsx": "preserve", + "baseUrl": "./", + "isolatedModules": true, + "paths": {} + }, + "include": ["src", "types"] +} From e33b278b47997f3dd4fcc18818ba8ab39ee7d89c Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:48:56 +0800 Subject: [PATCH 04/12] feat: add app csr test case --- .../integration/i18n/app-csr/modern.config.ts | 20 ++++ tests/integration/i18n/app-csr/package.json | 20 ++++ .../i18n/app-csr/src/i18n-app/App.tsx | 30 +++++ .../integration/i18n/app-csr/src/lang/App.tsx | 71 +++++++++++ .../i18n/app-csr/src/modern-app-env.d.ts | 2 + .../i18n/app-csr/src/modern.runtime.tsx | 27 +++++ .../i18n/app-csr/tests/index.test.ts | 110 ++++++++++++++++++ tests/integration/i18n/app-csr/tsconfig.json | 14 +++ 8 files changed, 294 insertions(+) create mode 100644 tests/integration/i18n/app-csr/modern.config.ts create mode 100644 tests/integration/i18n/app-csr/package.json create mode 100644 tests/integration/i18n/app-csr/src/i18n-app/App.tsx create mode 100644 tests/integration/i18n/app-csr/src/lang/App.tsx create mode 100644 tests/integration/i18n/app-csr/src/modern-app-env.d.ts create mode 100644 tests/integration/i18n/app-csr/src/modern.runtime.tsx create mode 100644 tests/integration/i18n/app-csr/tests/index.test.ts create mode 100644 tests/integration/i18n/app-csr/tsconfig.json diff --git a/tests/integration/i18n/app-csr/modern.config.ts b/tests/integration/i18n/app-csr/modern.config.ts new file mode 100644 index 000000000000..7dfa00a45c88 --- /dev/null +++ b/tests/integration/i18n/app-csr/modern.config.ts @@ -0,0 +1,20 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; + +export default defineConfig({ + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + localeDetectionByEntry: { + main: { + enable: false, + }, + }, + }, + }), + ], +}); diff --git a/tests/integration/i18n/app-csr/package.json b/tests/integration/i18n/app-csr/package.json new file mode 100644 index 000000000000..66b0dc586cf9 --- /dev/null +++ b/tests/integration/i18n/app-csr/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "name": "i18n-app", + "version": "2.66.0", + "scripts": { + "dev": "modern dev" + }, + "dependencies": { + "i18next":"25.2.1", + "react-i18next": "15.5.2", + "@modern-js/runtime": "workspace:*", + "@modern-js/plugin-i18n": "workspace:*", + "i18next-browser-languagedetector": "8.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*" + } +} diff --git a/tests/integration/i18n/app-csr/src/i18n-app/App.tsx b/tests/integration/i18n/app-csr/src/i18n-app/App.tsx new file mode 100644 index 000000000000..ec4ef58a4684 --- /dev/null +++ b/tests/integration/i18n/app-csr/src/i18n-app/App.tsx @@ -0,0 +1,30 @@ +import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; +import { BrowserRouter, Route, Routes } from '@modern-js/runtime/router'; +import i18next from 'i18next'; + +const App = () => { + const { changeLanguage } = useModernI18n(); + + return ( + +
+ + +
+ + {i18next.t('key')}} /> + {i18next.t('about')}} + /> + 404 + +
+ ); +}; + +export default App; diff --git a/tests/integration/i18n/app-csr/src/lang/App.tsx b/tests/integration/i18n/app-csr/src/lang/App.tsx new file mode 100644 index 000000000000..08d5d40b5690 --- /dev/null +++ b/tests/integration/i18n/app-csr/src/lang/App.tsx @@ -0,0 +1,71 @@ +import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime'; +import { + BrowserRouter, + Outlet, + Route, + Routes, +} from '@modern-js/runtime/router'; +import i18next from 'i18next'; + +const App = () => { + const { language, changeLanguage, supportedLanguages, isLanguageSupported } = + useModernI18n(); + + const handleLanguageChange = (newLang: string) => { + if (isLanguageSupported(newLang)) { + changeLanguage(newLang); + } + }; + + return ( + +
+

Current language: {language}

+

Supported languages: {supportedLanguages.join(', ')}

+ {supportedLanguages.map(lang => ( + + ))} +
+ + }> + + About +
{i18next.t('key')}
+ + } + /> + + Index +
{i18next.t('about')}
+ + } + /> + 404 +
+
+
+ ); +}; + +export default App; diff --git a/tests/integration/i18n/app-csr/src/modern-app-env.d.ts b/tests/integration/i18n/app-csr/src/modern-app-env.d.ts new file mode 100644 index 000000000000..8b882f336823 --- /dev/null +++ b/tests/integration/i18n/app-csr/src/modern-app-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/tests/integration/i18n/app-csr/src/modern.runtime.tsx b/tests/integration/i18n/app-csr/src/modern.runtime.tsx new file mode 100644 index 000000000000..691974c841fc --- /dev/null +++ b/tests/integration/i18n/app-csr/src/modern.runtime.tsx @@ -0,0 +1,27 @@ +import { defineRuntimeConfig } from '@modern-js/runtime'; +import i18next from 'i18next'; + +export default defineRuntimeConfig({ + i18n: { + i18nInstance: i18next, + initOptions: { + lng: 'en', + fallbackLng: 'en', + supportedLngs: ['zh', 'en'], + resources: { + en: { + translation: { + key: 'Hello World', + about: 'About', + }, + }, + zh: { + translation: { + key: '你好,世界', + about: '关于', + }, + }, + }, + }, + }, +}); diff --git a/tests/integration/i18n/app-csr/tests/index.test.ts b/tests/integration/i18n/app-csr/tests/index.test.ts new file mode 100644 index 000000000000..37ce10cceb2e --- /dev/null +++ b/tests/integration/i18n/app-csr/tests/index.test.ts @@ -0,0 +1,110 @@ +import path from 'path'; +import puppeteer, { type Browser, type Page } from 'puppeteer'; +import { + getPort, + killApp, + launchApp, + launchOptions, +} from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +describe('app-csr-i18n', () => { + let app: unknown; + let page: Page; + let browser: Browser; + let appPort: number; + beforeAll(async () => { + const appDir = projectDir; + appPort = await getPort(); + app = await launchApp(appDir, appPort); + + browser = await puppeteer.launch(launchOptions as any); + page = await browser.newPage(); + }); + afterAll(async () => { + if (browser) { + browser.close(); + } + if (app) { + await killApp(app); + } + }); + + test('main-index', async () => { + await page.goto(`http://localhost:${appPort}`, { + waitUntil: ['networkidle0'], + }); + const root = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, root); + expect(targetText?.trim()).toEqual('Hello World'); + await page.click('#zh-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const targetTextZh = await page.evaluate(el => el?.textContent, root); + expect(targetTextZh?.trim()).toEqual('你好,世界'); + }); + test('main-about', async () => { + await page.goto(`http://localhost:${appPort}/about`, { + waitUntil: ['networkidle0'], + }); + const rootAbout = await page.$('#about'); + const targetTextAbout = await page.evaluate( + el => el?.textContent, + rootAbout, + ); + expect(targetTextAbout?.trim()).toEqual('About'); + await page.click('#zh-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const targetTextAboutZh = await page.evaluate( + el => el?.textContent, + rootAbout, + ); + expect(targetTextAboutZh?.trim()).toEqual('关于'); + }); + test('lang-redirect-to-en', async () => { + await page.goto(`http://localhost:${appPort}/lang`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/lang/en`); + await page.goto(`http://localhost:${appPort}/lang/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/lang/en`); + await page.goto(`http://localhost:${appPort}/lang/about`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/lang/en/about`); + await page.goto(`http://localhost:${appPort}/lang/about/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/lang/en/about`); + }); + test('lang-zh', async () => { + await page.goto(`http://localhost:${appPort}/lang/zh`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('你好,世界'); + await page.goto(`http://localhost:${appPort}/lang/zh/about`, { + waitUntil: ['networkidle0'], + }); + const about = await page.$('#about'); + const targetTextAbout = await page.evaluate(el => el?.textContent, about); + expect(targetTextAbout?.trim()).toEqual('关于'); + }); + test('lang-en', async () => { + await page.goto(`http://localhost:${appPort}/lang/en`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('Hello World'); + await page.goto(`http://localhost:${appPort}/lang/en/about`, { + waitUntil: ['networkidle0'], + }); + const about = await page.$('#about'); + const targetTextAbout = await page.evaluate(el => el?.textContent, about); + expect(targetTextAbout?.trim()).toEqual('About'); + }); +}); diff --git a/tests/integration/i18n/app-csr/tsconfig.json b/tests/integration/i18n/app-csr/tsconfig.json new file mode 100644 index 000000000000..20bd5c24f7a7 --- /dev/null +++ b/tests/integration/i18n/app-csr/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "jsx": "preserve", + "baseUrl": "./", + "outDir": "dist", + "paths": { + "@/*": ["./src/*"], + "@shared/*": ["./shared/*"] + } + }, + "include": ["src", "tests", "modern.config.ts"] +} From 0140e43b06c74fc1aa345b630ab9538ad7dd4b1f Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:49:18 +0800 Subject: [PATCH 05/12] feat: add app ssr test case --- .../integration/i18n/app-ssr/modern.config.ts | 18 +++++ .../i18n/app-ssr/modern.ssg.config.ts | 25 +++++++ tests/integration/i18n/app-ssr/package.json | 22 ++++++ tests/integration/i18n/app-ssr/src/App.tsx | 22 ++++++ tests/integration/i18n/app-ssr/src/i18n.ts | 5 ++ .../i18n/app-ssr/src/modern-app-env.d.ts | 4 + .../i18n/app-ssr/src/modern.runtime.tsx | 24 ++++++ .../i18n/app-ssr/tests/index.test.ts | 74 +++++++++++++++++++ tests/integration/i18n/app-ssr/tsconfig.json | 14 ++++ 9 files changed, 208 insertions(+) create mode 100644 tests/integration/i18n/app-ssr/modern.config.ts create mode 100644 tests/integration/i18n/app-ssr/modern.ssg.config.ts create mode 100644 tests/integration/i18n/app-ssr/package.json create mode 100644 tests/integration/i18n/app-ssr/src/App.tsx create mode 100644 tests/integration/i18n/app-ssr/src/i18n.ts create mode 100644 tests/integration/i18n/app-ssr/src/modern-app-env.d.ts create mode 100644 tests/integration/i18n/app-ssr/src/modern.runtime.tsx create mode 100644 tests/integration/i18n/app-ssr/tests/index.test.ts create mode 100644 tests/integration/i18n/app-ssr/tsconfig.json diff --git a/tests/integration/i18n/app-ssr/modern.config.ts b/tests/integration/i18n/app-ssr/modern.config.ts new file mode 100644 index 000000000000..e0c3b0d56080 --- /dev/null +++ b/tests/integration/i18n/app-ssr/modern.config.ts @@ -0,0 +1,18 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; + +export default defineConfig({ + server: { + ssr: true, + }, + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + }, + }), + ], +}); diff --git a/tests/integration/i18n/app-ssr/modern.ssg.config.ts b/tests/integration/i18n/app-ssr/modern.ssg.config.ts new file mode 100644 index 000000000000..8a84a0683c35 --- /dev/null +++ b/tests/integration/i18n/app-ssr/modern.ssg.config.ts @@ -0,0 +1,25 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; +import { ssgPlugin } from '@modern-js/plugin-ssg'; + +export default defineConfig({ + server: { + ssr: process.env.NODE_ENV === 'development', + }, + output: { + ssg: { + routes: ['/zh', '/en'], + }, + }, + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + }, + }), + ssgPlugin(), + ], +}); diff --git a/tests/integration/i18n/app-ssr/package.json b/tests/integration/i18n/app-ssr/package.json new file mode 100644 index 000000000000..0e0842003392 --- /dev/null +++ b/tests/integration/i18n/app-ssr/package.json @@ -0,0 +1,22 @@ +{ + "private": true, + "name": "i18n-app-ssr", + "version": "2.66.0", + "scripts": { + "dev": "modern dev", + "build": "modern build -c modern.ssg.config.ts" + }, + "dependencies": { + "i18next":"25.2.1", + "react-i18next": "15.5.2", + "@modern-js/runtime": "workspace:*", + "@modern-js/plugin-i18n": "workspace:*", + "i18next-browser-languagedetector": "8.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*", + "@modern-js/plugin-ssg": "workspace:*" + } +} diff --git a/tests/integration/i18n/app-ssr/src/App.tsx b/tests/integration/i18n/app-ssr/src/App.tsx new file mode 100644 index 000000000000..f57c95b9bd1a --- /dev/null +++ b/tests/integration/i18n/app-ssr/src/App.tsx @@ -0,0 +1,22 @@ +import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; +import { useTranslation } from 'react-i18next'; + +const App = () => { + const { changeLanguage } = useModernI18n(); + const { t } = useTranslation(); + return ( + <> +
+ + +
+
{t('key')}
+ + ); +}; + +export default App; diff --git a/tests/integration/i18n/app-ssr/src/i18n.ts b/tests/integration/i18n/app-ssr/src/i18n.ts new file mode 100644 index 000000000000..18532df19be6 --- /dev/null +++ b/tests/integration/i18n/app-ssr/src/i18n.ts @@ -0,0 +1,5 @@ +import originalI18next from 'i18next'; + +const i18next = originalI18next.createInstance(); + +export { i18next }; diff --git a/tests/integration/i18n/app-ssr/src/modern-app-env.d.ts b/tests/integration/i18n/app-ssr/src/modern-app-env.d.ts new file mode 100644 index 000000000000..d8912d28ef5a --- /dev/null +++ b/tests/integration/i18n/app-ssr/src/modern-app-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module 'normalize-path'; diff --git a/tests/integration/i18n/app-ssr/src/modern.runtime.tsx b/tests/integration/i18n/app-ssr/src/modern.runtime.tsx new file mode 100644 index 000000000000..0ff24ee94c95 --- /dev/null +++ b/tests/integration/i18n/app-ssr/src/modern.runtime.tsx @@ -0,0 +1,24 @@ +import { defineRuntimeConfig } from '@modern-js/runtime'; +import { i18next } from './i18n'; + +export default defineRuntimeConfig({ + i18n: { + i18nInstance: i18next, + initOptions: { + resources: { + en: { + translation: { + key: 'Hello World', + about: 'About', + }, + }, + zh: { + translation: { + key: '你好,世界', + about: '关于', + }, + }, + }, + }, + }, +}); diff --git a/tests/integration/i18n/app-ssr/tests/index.test.ts b/tests/integration/i18n/app-ssr/tests/index.test.ts new file mode 100644 index 000000000000..69357ed82321 --- /dev/null +++ b/tests/integration/i18n/app-ssr/tests/index.test.ts @@ -0,0 +1,74 @@ +import path from 'path'; +import puppeteer, { type Browser, type Page } from 'puppeteer'; +import { + getPort, + killApp, + launchApp, + launchOptions, +} from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +describe('app-ssr-i18n', () => { + let app: unknown; + let page: Page; + let browser: Browser; + let appPort: number; + beforeAll(async () => { + const appDir = projectDir; + appPort = await getPort(); + app = await launchApp(appDir, appPort); + + browser = await puppeteer.launch(launchOptions as any); + page = await browser.newPage(); + }); + afterAll(async () => { + if (browser) { + browser.close(); + } + if (app) { + await killApp(app); + } + }); + + test('redirect-to-en', async () => { + await page.goto(`http://localhost:${appPort}`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + await page.goto(`http://localhost:${appPort}/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + }); + test('page-zh', async () => { + const response = await page.goto(`http://localhost:${appPort}/zh`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('你好,世界'); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('你好,世界'); + page.click('#en-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textEn = await page.$('#key'); + const targetTextEn = await page.evaluate(el => el?.textContent, textEn); + expect(targetTextEn?.trim()).toEqual('Hello World'); + }); + test('page-en', async () => { + const response = await page.goto(`http://localhost:${appPort}/en`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('Hello World'); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('Hello World'); + page.click('#zh-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textZh = await page.$('#key'); + const targetTextZh = await page.evaluate(el => el?.textContent, textZh); + expect(targetTextZh?.trim()).toEqual('你好,世界'); + }); +}); diff --git a/tests/integration/i18n/app-ssr/tsconfig.json b/tests/integration/i18n/app-ssr/tsconfig.json new file mode 100644 index 000000000000..20bd5c24f7a7 --- /dev/null +++ b/tests/integration/i18n/app-ssr/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "jsx": "preserve", + "baseUrl": "./", + "outDir": "dist", + "paths": { + "@/*": ["./src/*"], + "@shared/*": ["./shared/*"] + } + }, + "include": ["src", "tests", "modern.config.ts"] +} From 1907ca4ec12d5e9f1f6161a34b63a63f185b9dbe Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:49:40 +0800 Subject: [PATCH 06/12] feat: add router csr test case --- .../i18n/routes-csr/modern.config.ts | 15 +++ .../integration/i18n/routes-csr/package.json | 20 ++++ .../i18n/routes-csr/src/modern.runtime.tsx | 24 +++++ .../src/routes/[lang]/about/page.tsx | 5 + .../routes-csr/src/routes/[lang]/page.data.ts | 9 ++ .../routes-csr/src/routes/[lang]/page.tsx | 7 ++ .../i18n/routes-csr/src/routes/layout.tsx | 19 ++++ .../i18n/routes-csr/test/index.test.ts | 94 +++++++++++++++++++ .../integration/i18n/routes-csr/tsconfig.json | 14 +++ 9 files changed, 207 insertions(+) create mode 100644 tests/integration/i18n/routes-csr/modern.config.ts create mode 100644 tests/integration/i18n/routes-csr/package.json create mode 100644 tests/integration/i18n/routes-csr/src/modern.runtime.tsx create mode 100644 tests/integration/i18n/routes-csr/src/routes/[lang]/about/page.tsx create mode 100644 tests/integration/i18n/routes-csr/src/routes/[lang]/page.data.ts create mode 100644 tests/integration/i18n/routes-csr/src/routes/[lang]/page.tsx create mode 100644 tests/integration/i18n/routes-csr/src/routes/layout.tsx create mode 100644 tests/integration/i18n/routes-csr/test/index.test.ts create mode 100644 tests/integration/i18n/routes-csr/tsconfig.json diff --git a/tests/integration/i18n/routes-csr/modern.config.ts b/tests/integration/i18n/routes-csr/modern.config.ts new file mode 100644 index 000000000000..2db7278d38c7 --- /dev/null +++ b/tests/integration/i18n/routes-csr/modern.config.ts @@ -0,0 +1,15 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; + +export default defineConfig({ + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + }, + }), + ], +}); diff --git a/tests/integration/i18n/routes-csr/package.json b/tests/integration/i18n/routes-csr/package.json new file mode 100644 index 000000000000..59d5b6ecb517 --- /dev/null +++ b/tests/integration/i18n/routes-csr/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "name": "i18n-routes", + "version": "2.66.0", + "scripts": { + "dev": "modern dev" + }, + "dependencies": { + "i18next":"25.2.1", + "react-i18next": "15.5.2", + "@modern-js/runtime": "workspace:*", + "@modern-js/plugin-i18n": "workspace:*", + "i18next-browser-languagedetector": "8.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*" + } +} diff --git a/tests/integration/i18n/routes-csr/src/modern.runtime.tsx b/tests/integration/i18n/routes-csr/src/modern.runtime.tsx new file mode 100644 index 000000000000..9f859fa049a4 --- /dev/null +++ b/tests/integration/i18n/routes-csr/src/modern.runtime.tsx @@ -0,0 +1,24 @@ +import { defineRuntimeConfig } from '@modern-js/runtime'; +import i18next from 'i18next'; + +export default defineRuntimeConfig({ + i18n: { + i18nInstance: i18next, + initOptions: { + resources: { + en: { + translation: { + key: 'Hello World', + about: 'About', + }, + }, + zh: { + translation: { + key: '你好,世界', + about: '关于', + }, + }, + }, + }, + }, +}); diff --git a/tests/integration/i18n/routes-csr/src/routes/[lang]/about/page.tsx b/tests/integration/i18n/routes-csr/src/routes/[lang]/about/page.tsx new file mode 100644 index 000000000000..4a045fb5fc7b --- /dev/null +++ b/tests/integration/i18n/routes-csr/src/routes/[lang]/about/page.tsx @@ -0,0 +1,5 @@ +import i18next from 'i18next'; + +export default () => { + return
{i18next.t('about')}
; +}; diff --git a/tests/integration/i18n/routes-csr/src/routes/[lang]/page.data.ts b/tests/integration/i18n/routes-csr/src/routes/[lang]/page.data.ts new file mode 100644 index 000000000000..36e0017519db --- /dev/null +++ b/tests/integration/i18n/routes-csr/src/routes/[lang]/page.data.ts @@ -0,0 +1,9 @@ +import i18next from 'i18next'; +export interface ProfileData { + /* some types */ + data: string; +} + +export const loader = async ({ params }: any): Promise => { + return { data: i18next.t('key', { lng: params.lang || i18next.language }) }; +}; diff --git a/tests/integration/i18n/routes-csr/src/routes/[lang]/page.tsx b/tests/integration/i18n/routes-csr/src/routes/[lang]/page.tsx new file mode 100644 index 000000000000..413e1cea40d2 --- /dev/null +++ b/tests/integration/i18n/routes-csr/src/routes/[lang]/page.tsx @@ -0,0 +1,7 @@ +import { useLoaderData } from '@modern-js/runtime/router'; +import type { ProfileData } from './page.data'; + +export default () => { + const profileData = useLoaderData() as ProfileData; + return
{profileData.data}
; +}; diff --git a/tests/integration/i18n/routes-csr/src/routes/layout.tsx b/tests/integration/i18n/routes-csr/src/routes/layout.tsx new file mode 100644 index 000000000000..16d79950aba1 --- /dev/null +++ b/tests/integration/i18n/routes-csr/src/routes/layout.tsx @@ -0,0 +1,19 @@ +import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; +import { Outlet } from '@modern-js/runtime/router'; + +export default function Layout() { + const { changeLanguage } = useModernI18n(); + return ( +
+
+ + +
+ +
+ ); +} diff --git a/tests/integration/i18n/routes-csr/test/index.test.ts b/tests/integration/i18n/routes-csr/test/index.test.ts new file mode 100644 index 000000000000..720c61291a0c --- /dev/null +++ b/tests/integration/i18n/routes-csr/test/index.test.ts @@ -0,0 +1,94 @@ +import path from 'path'; +import puppeteer, { type Browser, type Page } from 'puppeteer'; +import { + getPort, + killApp, + launchApp, + launchOptions, +} from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +describe('router-csr-i18n', () => { + let app: unknown; + let page: Page; + let browser: Browser; + let appPort: number; + beforeAll(async () => { + const appDir = projectDir; + appPort = await getPort(); + app = await launchApp(appDir, appPort); + + browser = await puppeteer.launch(launchOptions as any); + page = await browser.newPage(); + }); + afterAll(async () => { + if (browser) { + browser.close(); + } + if (app) { + await killApp(app); + } + }); + + test('redirect-to-en', async () => { + await page.goto(`http://localhost:${appPort}`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + await page.goto(`http://localhost:${appPort}/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + await page.goto(`http://localhost:${appPort}/about`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en/about`); + await page.goto(`http://localhost:${appPort}/about/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en/about`); + }); + test('page-zh', async () => { + await page.goto(`http://localhost:${appPort}/zh`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('你好,世界'); + }); + test('page-en', async () => { + await page.goto(`http://localhost:${appPort}/en`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('Hello World'); + }); + test('page-zh-about', async () => { + await page.goto(`http://localhost:${appPort}/zh/about`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#about'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('关于'); + page.click('#en-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textEn = await page.$('#about'); + const targetTextEn = await page.evaluate(el => el?.textContent, textEn); + expect(targetTextEn?.trim()).toEqual('About'); + }); + test('page-en-about', async () => { + await page.goto(`http://localhost:${appPort}/en/about`, { + waitUntil: ['networkidle0'], + }); + const text = await page.$('#about'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('About'); + page.click('#zh-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textZh = await page.$('#about'); + const targetTextZh = await page.evaluate(el => el?.textContent, textZh); + expect(targetTextZh?.trim()).toEqual('关于'); + }); +}); diff --git a/tests/integration/i18n/routes-csr/tsconfig.json b/tests/integration/i18n/routes-csr/tsconfig.json new file mode 100644 index 000000000000..20bd5c24f7a7 --- /dev/null +++ b/tests/integration/i18n/routes-csr/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "jsx": "preserve", + "baseUrl": "./", + "outDir": "dist", + "paths": { + "@/*": ["./src/*"], + "@shared/*": ["./shared/*"] + } + }, + "include": ["src", "tests", "modern.config.ts"] +} From d76ef95c57240047bda46f73f2a924c7e244a11e Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:50:09 +0800 Subject: [PATCH 07/12] feat: add router ssr test case --- .../i18n/routes-ssr/modern.config.ts | 18 ++++ .../i18n/routes-ssr/modern.ssg.config.ts | 25 +++++ .../integration/i18n/routes-ssr/package.json | 22 ++++ tests/integration/i18n/routes-ssr/src/i18n.ts | 5 + .../i18n/routes-ssr/src/modern-app-env.d.ts | 4 + .../i18n/routes-ssr/src/modern.runtime.tsx | 24 +++++ .../src/routes/[lang]/about/page.tsx | 6 ++ .../routes-ssr/src/routes/[lang]/page.data.ts | 9 ++ .../routes-ssr/src/routes/[lang]/page.tsx | 11 ++ .../i18n/routes-ssr/src/routes/layout.tsx | 19 ++++ .../i18n/routes-ssr/test/index.test.ts | 102 ++++++++++++++++++ .../integration/i18n/routes-ssr/tsconfig.json | 14 +++ 12 files changed, 259 insertions(+) create mode 100644 tests/integration/i18n/routes-ssr/modern.config.ts create mode 100644 tests/integration/i18n/routes-ssr/modern.ssg.config.ts create mode 100644 tests/integration/i18n/routes-ssr/package.json create mode 100644 tests/integration/i18n/routes-ssr/src/i18n.ts create mode 100644 tests/integration/i18n/routes-ssr/src/modern-app-env.d.ts create mode 100644 tests/integration/i18n/routes-ssr/src/modern.runtime.tsx create mode 100644 tests/integration/i18n/routes-ssr/src/routes/[lang]/about/page.tsx create mode 100644 tests/integration/i18n/routes-ssr/src/routes/[lang]/page.data.ts create mode 100644 tests/integration/i18n/routes-ssr/src/routes/[lang]/page.tsx create mode 100644 tests/integration/i18n/routes-ssr/src/routes/layout.tsx create mode 100644 tests/integration/i18n/routes-ssr/test/index.test.ts create mode 100644 tests/integration/i18n/routes-ssr/tsconfig.json diff --git a/tests/integration/i18n/routes-ssr/modern.config.ts b/tests/integration/i18n/routes-ssr/modern.config.ts new file mode 100644 index 000000000000..e0c3b0d56080 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/modern.config.ts @@ -0,0 +1,18 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; + +export default defineConfig({ + server: { + ssr: true, + }, + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + }, + }), + ], +}); diff --git a/tests/integration/i18n/routes-ssr/modern.ssg.config.ts b/tests/integration/i18n/routes-ssr/modern.ssg.config.ts new file mode 100644 index 000000000000..f8f523621971 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/modern.ssg.config.ts @@ -0,0 +1,25 @@ +import { appTools, defineConfig } from '@modern-js/app-tools'; +import { i18nPlugin } from '@modern-js/plugin-i18n'; +import { ssgPlugin } from '@modern-js/plugin-ssg'; + +export default defineConfig({ + server: { + ssr: process.env.NODE_ENV === 'development', + }, + output: { + ssg: { + routes: ['/zh/about', '/en/about', '/en', '/zh'], + }, + }, + plugins: [ + appTools(), + i18nPlugin({ + localeDetection: { + enable: true, + languages: ['zh', 'en'], + fallbackLanguage: 'en', + }, + }), + ssgPlugin(), + ], +}); diff --git a/tests/integration/i18n/routes-ssr/package.json b/tests/integration/i18n/routes-ssr/package.json new file mode 100644 index 000000000000..4a6c466943f6 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/package.json @@ -0,0 +1,22 @@ +{ + "private": true, + "name": "i18n-routes-ssr", + "version": "2.66.0", + "scripts": { + "dev": "modern dev", + "build": "modern build -c modern.ssg.config.ts" + }, + "dependencies": { + "i18next":"25.2.1", + "react-i18next": "15.5.2", + "@modern-js/runtime": "workspace:*", + "@modern-js/plugin-i18n": "workspace:*", + "i18next-browser-languagedetector": "8.1.0", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@modern-js/app-tools": "workspace:*", + "@modern-js/plugin-ssg": "workspace:*" + } +} diff --git a/tests/integration/i18n/routes-ssr/src/i18n.ts b/tests/integration/i18n/routes-ssr/src/i18n.ts new file mode 100644 index 000000000000..18532df19be6 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/i18n.ts @@ -0,0 +1,5 @@ +import originalI18next from 'i18next'; + +const i18next = originalI18next.createInstance(); + +export { i18next }; diff --git a/tests/integration/i18n/routes-ssr/src/modern-app-env.d.ts b/tests/integration/i18n/routes-ssr/src/modern-app-env.d.ts new file mode 100644 index 000000000000..d8912d28ef5a --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/modern-app-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +declare module 'normalize-path'; diff --git a/tests/integration/i18n/routes-ssr/src/modern.runtime.tsx b/tests/integration/i18n/routes-ssr/src/modern.runtime.tsx new file mode 100644 index 000000000000..0ff24ee94c95 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/modern.runtime.tsx @@ -0,0 +1,24 @@ +import { defineRuntimeConfig } from '@modern-js/runtime'; +import { i18next } from './i18n'; + +export default defineRuntimeConfig({ + i18n: { + i18nInstance: i18next, + initOptions: { + resources: { + en: { + translation: { + key: 'Hello World', + about: 'About', + }, + }, + zh: { + translation: { + key: '你好,世界', + about: '关于', + }, + }, + }, + }, + }, +}); diff --git a/tests/integration/i18n/routes-ssr/src/routes/[lang]/about/page.tsx b/tests/integration/i18n/routes-ssr/src/routes/[lang]/about/page.tsx new file mode 100644 index 000000000000..bc4ba4bd85d8 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/routes/[lang]/about/page.tsx @@ -0,0 +1,6 @@ +import { useTranslation } from 'react-i18next'; + +export default () => { + const { t } = useTranslation(); + return
{t('about')}
; +}; diff --git a/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.data.ts b/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.data.ts new file mode 100644 index 000000000000..413440726b1a --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.data.ts @@ -0,0 +1,9 @@ +import { i18next } from '../../i18n'; +export interface ProfileData { + /* some types */ + data: string; +} + +export const loader = async ({ params }: any): Promise => { + return { data: i18next.t('key', { lng: params.lang || i18next.language }) }; +}; diff --git a/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.tsx b/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.tsx new file mode 100644 index 000000000000..e8cb25a15b79 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/routes/[lang]/page.tsx @@ -0,0 +1,11 @@ +import { useLoaderData } from '@modern-js/runtime/router'; +import type { ProfileData } from './page.data'; + +export default () => { + const profileData = useLoaderData() as ProfileData; + return ( + <> +
{profileData.data}
+ + ); +}; diff --git a/tests/integration/i18n/routes-ssr/src/routes/layout.tsx b/tests/integration/i18n/routes-ssr/src/routes/layout.tsx new file mode 100644 index 000000000000..16d79950aba1 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/src/routes/layout.tsx @@ -0,0 +1,19 @@ +import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; +import { Outlet } from '@modern-js/runtime/router'; + +export default function Layout() { + const { changeLanguage } = useModernI18n(); + return ( +
+
+ + +
+ +
+ ); +} diff --git a/tests/integration/i18n/routes-ssr/test/index.test.ts b/tests/integration/i18n/routes-ssr/test/index.test.ts new file mode 100644 index 000000000000..28b4a81400c4 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/test/index.test.ts @@ -0,0 +1,102 @@ +import path from 'path'; +import puppeteer, { type Browser, type Page } from 'puppeteer'; +import { + getPort, + killApp, + launchApp, + launchOptions, +} from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +describe('router-csr-i18n', () => { + let app: unknown; + let page: Page; + let browser: Browser; + let appPort: number; + beforeAll(async () => { + const appDir = projectDir; + appPort = await getPort(); + app = await launchApp(appDir, appPort); + + browser = await puppeteer.launch(launchOptions as any); + page = await browser.newPage(); + }); + afterAll(async () => { + if (browser) { + browser.close(); + } + if (app) { + await killApp(app); + } + }); + + test('redirect-to-en', async () => { + await page.goto(`http://localhost:${appPort}`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + await page.goto(`http://localhost:${appPort}/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en`); + await page.goto(`http://localhost:${appPort}/about`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en/about`); + await page.goto(`http://localhost:${appPort}/about/`, { + waitUntil: ['networkidle0'], + }); + expect(page.url()).toBe(`http://localhost:${appPort}/en/about`); + }); + test('page-zh', async () => { + const response = await page.goto(`http://localhost:${appPort}/zh`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('你好,世界'); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('你好,世界'); + }); + test('page-en', async () => { + const response = await page.goto(`http://localhost:${appPort}/en`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('Hello World'); + const text = await page.$('#key'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('Hello World'); + }); + test('page-zh-about', async () => { + const response = await page.goto(`http://localhost:${appPort}/zh/about`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('关于'); + const text = await page.$('#about'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('关于'); + page.click('#en-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textEn = await page.$('#about'); + const targetTextEn = await page.evaluate(el => el?.textContent, textEn); + expect(targetTextEn?.trim()).toEqual('About'); + }); + test('page-en-about', async () => { + const response = await page.goto(`http://localhost:${appPort}/en/about`, { + waitUntil: ['networkidle0'], + }); + const body = await response?.text(); + expect(body).toContain('About'); + const text = await page.$('#about'); + const targetText = await page.evaluate(el => el?.textContent, text); + expect(targetText?.trim()).toEqual('About'); + page.click('#zh-button'); + await new Promise(resolve => setTimeout(resolve, 2000)); + const textZh = await page.$('#about'); + const targetTextZh = await page.evaluate(el => el?.textContent, textZh); + expect(targetTextZh?.trim()).toEqual('关于'); + }); +}); diff --git a/tests/integration/i18n/routes-ssr/tsconfig.json b/tests/integration/i18n/routes-ssr/tsconfig.json new file mode 100644 index 000000000000..20bd5c24f7a7 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@modern-js/tsconfig/base", + "compilerOptions": { + "declaration": false, + "jsx": "preserve", + "baseUrl": "./", + "outDir": "dist", + "paths": { + "@/*": ["./src/*"], + "@shared/*": ["./shared/*"] + } + }, + "include": ["src", "tests", "modern.config.ts"] +} From e44944b381966c1337080837088f94116a8af9cf Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:50:26 +0800 Subject: [PATCH 08/12] feat: update lock file --- pnpm-lock.yaml | 282 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 242 insertions(+), 40 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8fde92327e1..f5e9db607519 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1195,6 +1195,70 @@ importers: specifier: ^5 version: 5.6.3 + packages/runtime/plugin-i18n: + dependencies: + '@modern-js/plugin': + specifier: workspace:* + version: link:../../toolkit/plugin + '@modern-js/server-runtime': + specifier: workspace:* + version: link:../../server/server-runtime + '@modern-js/types': + specifier: workspace:* + version: link:../../toolkit/types + '@modern-js/utils': + specifier: workspace:* + version: link:../../toolkit/utils + '@swc/helpers': + specifier: ^0.5.17 + version: 0.5.17 + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../solutions/app-tools + '@modern-js/runtime': + specifier: workspace:* + version: link:../plugin-runtime + '@scripts/build': + specifier: workspace:* + version: link:../../../scripts/build + '@scripts/jest-config': + specifier: workspace:* + version: link:../../../scripts/jest-config + '@testing-library/react': + specifier: ^13.4.0 + version: 13.4.0(@types/react@19.1.8)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 + '@types/node': + specifier: ^20 + version: 20.8.8 + i18next: + specifier: 25.2.1 + version: 25.2.1(typescript@5.6.3) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.8.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.8.8)(typescript@5.6.3)) + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-i18next: + specifier: 15.5.2 + version: 15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3) + ts-jest: + specifier: ^29.4.5 + version: 29.4.5(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.8.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.8.8)(typescript@5.6.3)))(typescript@5.6.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.8.8)(typescript@5.6.3) + typescript: + specifier: ^5 + version: 5.6.3 + packages/runtime/plugin-image: dependencies: '@rsbuild-image/core': @@ -1699,46 +1763,6 @@ importers: specifier: ^5 version: 5.6.3 - packages/server/plugin-worker: - dependencies: - '@modern-js/prod-server': - specifier: workspace:* - version: link:../prod-server - '@modern-js/server-utils': - specifier: workspace:* - version: link:../utils - '@modern-js/utils': - specifier: workspace:* - version: link:../../toolkit/utils - '@swc/helpers': - specifier: ^0.5.17 - version: 0.5.17 - devDependencies: - '@modern-js/app-tools': - specifier: workspace:* - version: link:../../solutions/app-tools - '@modern-js/types': - specifier: workspace:* - version: link:../../toolkit/types - '@scripts/build': - specifier: workspace:* - version: link:../../../scripts/build - '@scripts/jest-config': - specifier: workspace:* - version: link:../../../scripts/jest-config - '@types/jest': - specifier: ^29.5.14 - version: 29.5.14 - '@types/node': - specifier: ^20 - version: 20.8.8 - jest: - specifier: ^29.7.0 - version: 29.7.0(@types/node@20.8.8)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.31(@swc/helpers@0.5.17))(@types/node@20.8.8)(typescript@5.6.3)) - typescript: - specifier: ^5 - version: 5.6.3 - packages/server/prod-server: dependencies: '@modern-js/runtime-utils': @@ -3798,6 +3822,124 @@ importers: specifier: ^19.2.0 version: 19.2.0(react@19.2.0) + tests/integration/i18n/app-csr: + dependencies: + '@modern-js/plugin-i18n': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-i18n + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-runtime + i18next: + specifier: 25.2.1 + version: 25.2.1(typescript@5.6.3) + i18next-browser-languagedetector: + specifier: 8.1.0 + version: 8.1.0 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-i18next: + specifier: 15.5.2 + version: 15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3) + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../../packages/solutions/app-tools + + tests/integration/i18n/app-ssr: + dependencies: + '@modern-js/plugin-i18n': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-i18n + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-runtime + i18next: + specifier: 25.2.1 + version: 25.2.1(typescript@5.6.3) + i18next-browser-languagedetector: + specifier: 8.1.0 + version: 8.1.0 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-i18next: + specifier: 15.5.2 + version: 15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3) + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../../packages/solutions/app-tools + '@modern-js/plugin-ssg': + specifier: workspace:* + version: link:../../../../packages/cli/plugin-ssg + + tests/integration/i18n/routes-csr: + dependencies: + '@modern-js/plugin-i18n': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-i18n + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-runtime + i18next: + specifier: 25.2.1 + version: 25.2.1(typescript@5.6.3) + i18next-browser-languagedetector: + specifier: 8.1.0 + version: 8.1.0 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-i18next: + specifier: 15.5.2 + version: 15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3) + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../../packages/solutions/app-tools + + tests/integration/i18n/routes-ssr: + dependencies: + '@modern-js/plugin-i18n': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-i18n + '@modern-js/runtime': + specifier: workspace:* + version: link:../../../../packages/runtime/plugin-runtime + i18next: + specifier: 25.2.1 + version: 25.2.1(typescript@5.6.3) + i18next-browser-languagedetector: + specifier: 8.1.0 + version: 8.1.0 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-i18next: + specifier: 15.5.2 + version: 15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3) + devDependencies: + '@modern-js/app-tools': + specifier: workspace:* + version: link:../../../../packages/solutions/app-tools + '@modern-js/plugin-ssg': + specifier: workspace:* + version: link:../../../../packages/cli/plugin-ssg + tests/integration/image-component: dependencies: '@modern-js/image': @@ -11986,6 +12128,9 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} hasBin: true + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} @@ -12087,6 +12232,17 @@ packages: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} + i18next-browser-languagedetector@8.1.0: + resolution: {integrity: sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==} + + i18next@25.2.1: + resolution: {integrity: sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==} + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -14554,6 +14710,22 @@ packages: peerDependencies: react: '>=16.3.0' + react-i18next@15.5.2: + resolution: {integrity: sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -16211,6 +16383,10 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} @@ -24334,6 +24510,10 @@ snapshots: relateurl: 0.2.7 terser: 5.44.0 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + html-tags@3.3.1: {} html-to-text@9.0.5: @@ -24455,6 +24635,16 @@ snapshots: hyperdyperid@1.2.0: {} + i18next-browser-languagedetector@8.1.0: + dependencies: + '@babel/runtime': 7.28.4 + + i18next@25.2.1(typescript@5.6.3): + dependencies: + '@babel/runtime': 7.28.4 + optionalDependencies: + typescript: 5.6.3 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -27736,6 +27926,16 @@ snapshots: react-fast-compare: 3.2.0 react-side-effect: 2.1.2(react@19.2.0) + react-i18next@15.5.2(i18next@25.2.1(typescript@5.6.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.6.3): + dependencies: + '@babel/runtime': 7.28.4 + html-parse-stringify: 3.0.1 + i18next: 25.2.1(typescript@5.6.3) + react: 19.2.0 + optionalDependencies: + react-dom: 19.2.0(react@19.2.0) + typescript: 5.6.3 + react-is@16.13.1: {} react-is@17.0.2: {} @@ -29671,6 +29871,8 @@ snapshots: - supports-color - terser + void-elements@3.1.0: {} + vscode-jsonrpc@8.2.0: {} vscode-languageserver-protocol@3.17.5: From 1838793e23f37390d9d72b025cefee79da3eb79e Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 16:58:21 +0800 Subject: [PATCH 09/12] feat: add ssg test case --- .../i18n/app-ssr/tests/ssg.test.ts | 21 +++++++++++++++ .../i18n/routes-ssr/test/ssg.test.ts | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/integration/i18n/app-ssr/tests/ssg.test.ts create mode 100644 tests/integration/i18n/routes-ssr/test/ssg.test.ts diff --git a/tests/integration/i18n/app-ssr/tests/ssg.test.ts b/tests/integration/i18n/app-ssr/tests/ssg.test.ts new file mode 100644 index 000000000000..164869d44534 --- /dev/null +++ b/tests/integration/i18n/app-ssr/tests/ssg.test.ts @@ -0,0 +1,21 @@ +import path, { join } from 'path'; +import { fs } from '@modern-js/utils'; +import { modernBuild } from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +jest.setTimeout(1000 * 60 * 2); + +describe('ssg', () => { + test('should simple ssg work correctly', async () => { + const appDir = projectDir; + await modernBuild(appDir, ['--config', 'modern.ssg.config.ts']); + + const zhHtmlPath = path.join(appDir, './dist/html/main/zh/index.html'); + const enHtmlPath = path.join(appDir, './dist/html/main/en/index.html'); + const zhContent = fs.readFileSync(zhHtmlPath, 'utf-8'); + const enContent = fs.readFileSync(enHtmlPath, 'utf-8'); + expect(zhContent).toMatch('你好,世界'); + expect(enContent).toMatch('Hello World'); + }); +}); diff --git a/tests/integration/i18n/routes-ssr/test/ssg.test.ts b/tests/integration/i18n/routes-ssr/test/ssg.test.ts new file mode 100644 index 000000000000..d2737b888b97 --- /dev/null +++ b/tests/integration/i18n/routes-ssr/test/ssg.test.ts @@ -0,0 +1,27 @@ +import path, { join } from 'path'; +import { fs } from '@modern-js/utils'; +import { modernBuild } from '../../../../utils/modernTestUtils'; + +const projectDir = path.resolve(__dirname, '..'); + +jest.setTimeout(1000 * 60 * 2); + +describe('ssg', () => { + test('should simple ssg work correctly', async () => { + const appDir = projectDir; + await modernBuild(appDir, ['--config', 'modern.ssg.config.ts']); + + const zhAboutHtmlPath = path.join( + appDir, + './dist/html/main/zh/about/index.html', + ); + const enAboutHtmlPath = path.join( + appDir, + './dist/html/main/en/about/index.html', + ); + const zhAboutContent = fs.readFileSync(zhAboutHtmlPath, 'utf-8'); + const enAboutContent = fs.readFileSync(enAboutHtmlPath, 'utf-8'); + expect(zhAboutContent).toMatch('关于'); + expect(enAboutContent).toMatch('About'); + }); +}); From 79380d7edcad493012086aa36c27f27d02cbe11c Mon Sep 17 00:00:00 2001 From: caohuilin Date: Fri, 17 Oct 2025 17:09:56 +0800 Subject: [PATCH 10/12] fix: package.json config and comments --- packages/runtime/plugin-i18n/package.json | 24 +++---- packages/runtime/plugin-i18n/src/index.ts | 1 - .../plugin-i18n/src/runtime/context.tsx | 65 ++++--------------- .../runtime/plugin-i18n/src/server/index.ts | 12 ++-- 4 files changed, 30 insertions(+), 72 deletions(-) delete mode 100644 packages/runtime/plugin-i18n/src/index.ts diff --git a/packages/runtime/plugin-i18n/package.json b/packages/runtime/plugin-i18n/package.json index b6537511cbda..c3ad032327c4 100644 --- a/packages/runtime/plugin-i18n/package.json +++ b/packages/runtime/plugin-i18n/package.json @@ -7,7 +7,7 @@ "repository": { "type": "git", "url": "https://github.com/web-infra-dev/modern.js", - "directory": "packages/runtime/plugin-runtime" + "directory": "packages/runtime/plugin-i18n" }, "license": "MIT", "keywords": [ @@ -20,16 +20,15 @@ "engines": { "node": ">=20" }, - "jsnext:source": "./src/index.ts", - "types": "./src/index.ts", - "main": "./dist/cjs/index.js", - "module": "./dist/esm/index.js", + "jsnext:source": "./src/cli/index.ts", + "types": "./src/cli/index.ts", + "main": "./dist/cjs/cli/index.js", + "module": "./dist/esm/cli/index.js", "exports": { ".": { - "types": "./dist/types/index.d.ts", - "react-server": "./dist/esm/react-server.js", - "jsnext:source": "./src/index.ts", - "default": "./dist/esm/index.js" + "types": "./dist/types/cli/index.d.ts", + "jsnext:source": "./src/cli/index.ts", + "default": "./dist/cjs/cli/index.js" }, "./package.json": "./package.json", "./cli": { @@ -56,10 +55,7 @@ "typesVersions": { "*": { ".": [ - "./dist/types/index.d.ts" - ], - "types": [ - "./dist/types/types.d.ts" + "./dist/types/cli/index.d.ts" ], "cli": [ "./dist/types/cli/index.d.ts" @@ -125,6 +121,6 @@ "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public", - "types": "./dist/types/index" + "types": "./dist/types/cli/index.d.ts" } } diff --git a/packages/runtime/plugin-i18n/src/index.ts b/packages/runtime/plugin-i18n/src/index.ts deleted file mode 100644 index bf3b7e5f57f3..000000000000 --- a/packages/runtime/plugin-i18n/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './cli'; diff --git a/packages/runtime/plugin-i18n/src/runtime/context.tsx b/packages/runtime/plugin-i18n/src/runtime/context.tsx index e52e689eaa58..185ecb93923e 100644 --- a/packages/runtime/plugin-i18n/src/runtime/context.tsx +++ b/packages/runtime/plugin-i18n/src/runtime/context.tsx @@ -47,56 +47,6 @@ export interface UseModernI18nReturn { isLanguageSupported: (lang: string) => boolean; } -/** - * Hook for accessing i18n functionality in Modern.js applications. - * - * This hook provides: - * - Current language from URL params or i18n context - * - changeLanguage function that updates both i18n instance and URL - * - Direct access to the i18n instance - * - List of supported languages - * - Helper function to check if a language is supported - * - * @param options - Optional configuration to override context settings - * @returns Object containing i18n functionality and utilities - * - * @example - * ```tsx - * import { useModernI18n } from '@modern-js/plugin-i18n/runtime'; - * - * function MyComponent() { - * const { - * language, - * changeLanguage, - * i18nInstance, - * supportedLanguages, - * isLanguageSupported - * } = useModernI18n(); - * - * const handleLanguageChange = (newLang: string) => { - * if (isLanguageSupported(newLang)) { - * changeLanguage(newLang); - * } - * }; - * - * return ( - *
- *

Current language: {language}

- *

Supported languages: {supportedLanguages.join(', ')}

- * {supportedLanguages.map(lang => ( - * - * ))} - *
- * ); - * } - * ``` - */ // Safe hook wrapper to handle cases where router context is not available const useRouterHooks = () => { try { @@ -123,6 +73,19 @@ const useRouterHooks = () => { } }; +/** + * Hook for accessing i18n functionality in Modern.js applications. + * + * This hook provides: + * - Current language from URL params or i18n context + * - changeLanguage function that updates both i18n instance and URL + * - Direct access to the i18n instance + * - List of supported languages + * - Helper function to check if a language is supported + * + * @param options - Optional configuration to override context settings + * @returns Object containing i18n functionality and utilities + */ export const useModernI18n = ( options: UseModernI18nOptions = {}, ): UseModernI18nReturn => { @@ -148,7 +111,7 @@ export const useModernI18n = ( } = options; // Get router hooks safely - const { navigate, location, params, hasRouter } = useRouterHooks(); + const { navigate, location, hasRouter } = useRouterHooks(); // Get current language from context (which reflects the actual current language) // URL params might be stale after language changes, so we prioritize the context language diff --git a/packages/runtime/plugin-i18n/src/server/index.ts b/packages/runtime/plugin-i18n/src/server/index.ts index 3a9df323142f..d1f8d82bfa3b 100644 --- a/packages/runtime/plugin-i18n/src/server/index.ts +++ b/packages/runtime/plugin-i18n/src/server/index.ts @@ -16,8 +16,8 @@ const getLanguageFromPath = ( const url = new URL(req.url, `http://${req.headers.host}`); const pathname = url.pathname; - // 移除 urlPath 前缀,获取剩余路径 - // urlPath 格式为 /lang/*,需要移除 /lang 部分 + // Remove urlPath prefix to get remaining path + // urlPath format is /lang/*, need to remove /lang part const basePath = urlPath.replace('/*', ''); const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) @@ -42,7 +42,7 @@ const buildLocalizedUrl = ( const url = new URL(req.url, `http://${req.headers.host}`); const pathname = url.pathname; - // 移除 urlPath 前缀,获取剩余路径 + // Remove urlPath prefix to get remaining path const basePath = urlPath.replace('/*', ''); const remainingPath = pathname.startsWith(basePath) ? pathname.slice(basePath.length) @@ -51,15 +51,15 @@ const buildLocalizedUrl = ( const segments = remainingPath.split('/').filter(Boolean); if (segments.length > 0 && languages.includes(segments[0])) { - // 替换现有的语言前缀 + // Replace existing language prefix segments[0] = language; } else { - // 如果路径不以语言开头,添加语言前缀 + // If path doesn't start with language, add language prefix segments.unshift(language); } const newPathname = `/${segments.join('/')}`; - // 处理根路径的情况,避免出现 //en 这样的双斜杠 + // Handle root path case to avoid double slashes like //en const localizedUrl = basePath === '/' ? newPathname + url.search From 5835ae56d7ef0eee86ea60d3bc3027c64a43cc00 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Mon, 20 Oct 2025 18:53:43 +0800 Subject: [PATCH 11/12] fix: comments --- .../plugin-i18n/src/runtime/I18nLink.tsx | 23 +++++++------------ .../runtime/plugin-i18n/src/server/index.ts | 7 +++--- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx b/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx index caf836b26660..69c07ffabdbe 100644 --- a/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx +++ b/packages/runtime/plugin-i18n/src/runtime/I18nLink.tsx @@ -1,3 +1,4 @@ +import { Link, useInRouterContext, useParams } from '@modern-js/runtime/router'; import type React from 'react'; import { useModernI18n } from './context'; import { buildLocalizedUrl } from './utils'; @@ -23,22 +24,14 @@ export interface I18nLinkProps { * Home * ``` */ -// Safe hook wrapper for router hooks +// Use static imports to avoid breaking router tree-shaking. Detect router context via useInRouterContext. const useRouterHooks = () => { - try { - const { Link, useParams } = require('@modern-js/runtime/router'); - return { - Link, - params: useParams(), - hasRouter: true, - }; - } catch (error) { - return { - Link: null, - params: {}, - hasRouter: false, - }; - } + const inRouter = useInRouterContext(); + return { + Link: inRouter ? Link : null, + params: inRouter ? useParams() : ({} as any), + hasRouter: inRouter, + }; }; export const I18nLink: React.FC = ({ diff --git a/packages/runtime/plugin-i18n/src/server/index.ts b/packages/runtime/plugin-i18n/src/server/index.ts index d1f8d82bfa3b..181429c51838 100644 --- a/packages/runtime/plugin-i18n/src/server/index.ts +++ b/packages/runtime/plugin-i18n/src/server/index.ts @@ -39,7 +39,7 @@ const buildLocalizedUrl = ( language: string, languages: string[], ): string => { - const url = new URL(req.url, `http://${req.headers.host}`); + const url = new URL(req.url); const pathname = url.pathname; // Remove urlPath prefix to get remaining path @@ -60,10 +60,9 @@ const buildLocalizedUrl = ( const newPathname = `/${segments.join('/')}`; // Handle root path case to avoid double slashes like //en + const suffix = `${url.search}${url.hash}`; const localizedUrl = - basePath === '/' - ? newPathname + url.search - : basePath + newPathname + url.search; + basePath === '/' ? newPathname + suffix : basePath + newPathname + suffix; return localizedUrl; }; From dea0b9f04a6996327e0f31fdc86f1bd6d12b30e3 Mon Sep 17 00:00:00 2001 From: caohuilin Date: Mon, 20 Oct 2025 19:03:10 +0800 Subject: [PATCH 12/12] feat: test case time use 3000 --- tests/integration/i18n/app-csr/tests/index.test.ts | 4 ++-- tests/integration/i18n/app-ssr/tests/index.test.ts | 4 ++-- tests/integration/i18n/routes-csr/test/index.test.ts | 4 ++-- tests/integration/i18n/routes-ssr/test/index.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/i18n/app-csr/tests/index.test.ts b/tests/integration/i18n/app-csr/tests/index.test.ts index 37ce10cceb2e..db1179d401aa 100644 --- a/tests/integration/i18n/app-csr/tests/index.test.ts +++ b/tests/integration/i18n/app-csr/tests/index.test.ts @@ -39,7 +39,7 @@ describe('app-csr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, root); expect(targetText?.trim()).toEqual('Hello World'); await page.click('#zh-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const targetTextZh = await page.evaluate(el => el?.textContent, root); expect(targetTextZh?.trim()).toEqual('你好,世界'); }); @@ -54,7 +54,7 @@ describe('app-csr-i18n', () => { ); expect(targetTextAbout?.trim()).toEqual('About'); await page.click('#zh-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const targetTextAboutZh = await page.evaluate( el => el?.textContent, rootAbout, diff --git a/tests/integration/i18n/app-ssr/tests/index.test.ts b/tests/integration/i18n/app-ssr/tests/index.test.ts index 69357ed82321..7e9f8d021adb 100644 --- a/tests/integration/i18n/app-ssr/tests/index.test.ts +++ b/tests/integration/i18n/app-ssr/tests/index.test.ts @@ -51,7 +51,7 @@ describe('app-ssr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('你好,世界'); page.click('#en-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textEn = await page.$('#key'); const targetTextEn = await page.evaluate(el => el?.textContent, textEn); expect(targetTextEn?.trim()).toEqual('Hello World'); @@ -66,7 +66,7 @@ describe('app-ssr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('Hello World'); page.click('#zh-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textZh = await page.$('#key'); const targetTextZh = await page.evaluate(el => el?.textContent, textZh); expect(targetTextZh?.trim()).toEqual('你好,世界'); diff --git a/tests/integration/i18n/routes-csr/test/index.test.ts b/tests/integration/i18n/routes-csr/test/index.test.ts index 720c61291a0c..c945c07dc0b1 100644 --- a/tests/integration/i18n/routes-csr/test/index.test.ts +++ b/tests/integration/i18n/routes-csr/test/index.test.ts @@ -73,7 +73,7 @@ describe('router-csr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('关于'); page.click('#en-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textEn = await page.$('#about'); const targetTextEn = await page.evaluate(el => el?.textContent, textEn); expect(targetTextEn?.trim()).toEqual('About'); @@ -86,7 +86,7 @@ describe('router-csr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('About'); page.click('#zh-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textZh = await page.$('#about'); const targetTextZh = await page.evaluate(el => el?.textContent, textZh); expect(targetTextZh?.trim()).toEqual('关于'); diff --git a/tests/integration/i18n/routes-ssr/test/index.test.ts b/tests/integration/i18n/routes-ssr/test/index.test.ts index 28b4a81400c4..1150c2b90504 100644 --- a/tests/integration/i18n/routes-ssr/test/index.test.ts +++ b/tests/integration/i18n/routes-ssr/test/index.test.ts @@ -79,7 +79,7 @@ describe('router-csr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('关于'); page.click('#en-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textEn = await page.$('#about'); const targetTextEn = await page.evaluate(el => el?.textContent, textEn); expect(targetTextEn?.trim()).toEqual('About'); @@ -94,7 +94,7 @@ describe('router-csr-i18n', () => { const targetText = await page.evaluate(el => el?.textContent, text); expect(targetText?.trim()).toEqual('About'); page.click('#zh-button'); - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 3000)); const textZh = await page.$('#about'); const targetTextZh = await page.evaluate(el => el?.textContent, textZh); expect(targetTextZh?.trim()).toEqual('关于');