diff --git a/spx-gui/src/utils/i18n/README.md b/spx-gui/src/utils/i18n/README.md
new file mode 100644
index 000000000..79c399eb7
--- /dev/null
+++ b/spx-gui/src/utils/i18n/README.md
@@ -0,0 +1,89 @@
+# i18n
+
+Simple i18n tool for vue.
+
+## Usage
+
+### Init & set language
+
+```ts
+import { createI18n, useI18n } from './utils/i18n'
+
+// Install in Vue app
+app.use(
+ createI18n({
+ lang: 'en'
+ })
+)
+
+// set language
+const i18n = useI18n()
+i18n.setLang('zh')
+```
+
+### Do translation in template of SFC
+
+```vue
+
+```
+
+P.S. To avoid naming conflict with vue-i18n, we named the global property as `_t`. After vue-i18n removed from the project, we will rename it as `$t`.
+
+### Do translation in setup script
+
+```ts
+import { useI18n } from '@/utils/i18n'
+
+const { t } = useI18n()
+
+const signoutText = t({ en: 'Sign out', zh: '登出' })
+```
+
+### Function Locale Message
+
+Function-locale-messages are messages that extra information are needed when translating. For example:
+
+```ts
+const projectSummaryMessage: FunctionLocaleMessage<[num: number]> = {
+ en: num => `You have ${num} project${num > 1 ? 's' : ''}`,
+ zh: num => `你有 ${num} 个项目`
+}
+
+const projectSummary = t(projectSummaryMessage, 3) // "You have 3 projects" / "你有 3 个项目"
+```
+
+It's like [interpolations](https://vue-i18n.intlify.dev/guide/essentials/syntax.html#interpolations) in vue-i18n, but simpler & more powerful.
+
+### `mapMessage`
+
+Messages are more complex than texts, which makes messages operation more difficult that text operation. Fo example, we can join texts simply with string `+` or template strings (or even array `join`):
+
+```ts
+const helloText = 'Hello'
+console.log(helloText + ' ' + user.name)
+```
+
+But it is not easy to do similar thing with messages. `mapMessage` aims to help:
+
+```ts
+const helloMessage = {
+ en: 'Hello',
+ zh: '你好'
+}
+const resultMessage = mapMessage(helloMessage, hello => hello + ' ' + user.name)
+console.log(t(resultMessage))
+```
+
+We can also use `mapMessage` with function-locale-messages:
+
+```ts
+const projectSummaryMessage: FunctionLocaleMessage<[num: number]> = {
+ en: num => `You have ${num} project${num > 1 ? 's' : ''}`,
+ zh: num => `你有 ${num} 个项目`
+}
+
+const resultMessage = mapMessage(projectSummaryMessage, f => f(3))
+console.log(t(resultMessage))
+```
\ No newline at end of file
diff --git a/spx-gui/src/utils/i18n.ts b/spx-gui/src/utils/i18n/index.ts
similarity index 96%
rename from spx-gui/src/utils/i18n.ts
rename to spx-gui/src/utils/i18n/index.ts
index ff506e492..caf6848c2 100644
--- a/spx-gui/src/utils/i18n.ts
+++ b/spx-gui/src/utils/i18n/index.ts
@@ -74,7 +74,7 @@ export function useI18n() {
return i18n
}
-export function mapMessageValues, T>(
+export function mapMessage, T>(
message: M,
process: (value: M[keyof M], lang: Lang) => T
): Record {