diff --git a/.ls-lint.yml b/.ls-lint.yml
index 260343b46..4612a8008 100644
--- a/.ls-lint.yml
+++ b/.ls-lint.yml
@@ -4,3 +4,6 @@ ls:
.d.ts: camelCase
.spec.ts: camelCase
.vue: PascalCase
+
+ignore:
+ - packages/components/i18n/languages
diff --git a/docs/i18n.zh.md b/docs/i18n.zh.md
new file mode 100755
index 000000000..6f7fd687c
--- /dev/null
+++ b/docs/i18n.zh.md
@@ -0,0 +1,44 @@
+---
+order: 4
+title: 国际化
+---
+
+目前的默认文案是中文(`zh-CN`),如果需要使用其他语言,可以在初始化时进行配置,也可以在运行时切换,可以参考下面的方案。
+
+## 初始化
+
+```typescript
+import { useI18n } from '@idux/components'
+import { en_US } from '@idux/components/i18n/languages'
+
+
+useI18n(en_US)
+
+```
+
+## 运行时切换
+
+```typescript
+import { addI18n, useI18n } from '@idux/components'
+import { es_ES, en_US } from '@idux/components/i18n/languages'
+
+// 首先需要先添加多语言包
+addI18n([es_ES, en_US])
+
+useI18n('es_ES')
+
+// 运行时切换
+setTimeout(()=> useI18n('en_US'), 3000)
+
+```
+
+注意:`es_ES`, `en_US` 是语言包名称,以下表格也遵循同样的规则。
+
+## 支持语言
+
+| 语言 | 语言包名 |
+| ---------------- | ------ |
+| 英语(美式) | en_US |
+| 西班牙语 | es_ES |
+| 中文简体 | zh_CN |
+| 中文繁体 | zh_TW |
diff --git a/packages/components/core/logger/index.ts b/packages/components/core/logger/index.ts
index d095dee08..72f519dfe 100644
--- a/packages/components/core/logger/index.ts
+++ b/packages/components/core/logger/index.ts
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
+/* istanbul ignore file */
import { IDUX_COMPONENTS_PREFIX } from '../constant'
import { isDevMode } from '../utils'
diff --git a/packages/components/core/utils/installComponent.ts b/packages/components/core/utils/installComponent.ts
index 8fcc9b919..de1e2a473 100644
--- a/packages/components/core/utils/installComponent.ts
+++ b/packages/components/core/utils/installComponent.ts
@@ -1,3 +1,4 @@
+/* istanbul ignore file */
import type { App, DefineComponent } from 'vue'
export const installComponent = (component: DefineComponent) => {
diff --git a/packages/components/i18n/__tests__/useI18n.spec.ts b/packages/components/i18n/__tests__/useI18n.spec.ts
new file mode 100644
index 000000000..815fc7413
--- /dev/null
+++ b/packages/components/i18n/__tests__/useI18n.spec.ts
@@ -0,0 +1,55 @@
+/* eslint-disable camelcase */
+import { flushPromises, mount } from '@vue/test-utils'
+import { es_ES, en_US, zh_CN } from '../languages'
+import { addI18n, getI18n, useI18n } from '../useI18n'
+
+const Comp = {
+ template: `
{{globalI18n.placeholder}}
`,
+ setup() {
+ const globalI18n = getI18n('global')
+ return { globalI18n }
+ },
+}
+
+describe('useI18n.ts', () => {
+ test('default zh_CN work', async () => {
+ const wrapper = mount(Comp)
+ expect(wrapper.text()).toEqual(zh_CN.global.placeholder)
+ })
+
+ test('addI18n work', async () => {
+ const wrapper = mount(Comp)
+ addI18n(es_ES)
+ useI18n('es_ES')
+ await flushPromises()
+
+ expect(wrapper.text()).toEqual(es_ES.global.placeholder)
+
+ addI18n([en_US, zh_CN])
+ useI18n('en_US')
+ await flushPromises()
+ expect(wrapper.text()).toEqual(en_US.global.placeholder)
+ })
+
+ test('useI18n work', async () => {
+ const wrapper = mount(Comp)
+ useI18n(es_ES)
+ await flushPromises()
+
+ expect(wrapper.text()).toEqual(es_ES.global.placeholder)
+
+ useI18n('zh-CN')
+ await flushPromises()
+ expect(wrapper.text()).toEqual(zh_CN.global.placeholder)
+
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
+ useI18n('zh-TW')
+ expect(warnSpy).toBeCalledTimes(1)
+ })
+
+ test('getI18n work', async () => {
+ const i18n = getI18n()
+
+ expect(i18n.value).toEqual(zh_CN)
+ })
+})
diff --git a/packages/components/i18n/index.ts b/packages/components/i18n/index.ts
new file mode 100644
index 000000000..5f99fd487
--- /dev/null
+++ b/packages/components/i18n/index.ts
@@ -0,0 +1,2 @@
+export * from './types'
+export * from './useI18n'
diff --git a/packages/components/i18n/languages/en_US.ts b/packages/components/i18n/languages/en_US.ts
new file mode 100644
index 000000000..26fcf8437
--- /dev/null
+++ b/packages/components/i18n/languages/en_US.ts
@@ -0,0 +1,9 @@
+import { Locale } from '../types'
+
+// eslint-disable-next-line camelcase
+export const en_US: Locale = {
+ type: 'en_US',
+ global: {
+ placeholder: 'Please select',
+ },
+}
diff --git a/packages/components/i18n/languages/es_ES.ts b/packages/components/i18n/languages/es_ES.ts
new file mode 100755
index 000000000..4d173bfe0
--- /dev/null
+++ b/packages/components/i18n/languages/es_ES.ts
@@ -0,0 +1,9 @@
+import { Locale } from '../types'
+
+// eslint-disable-next-line camelcase
+export const es_ES: Locale = {
+ type: 'es_ES',
+ global: {
+ placeholder: 'Seleccione',
+ },
+}
diff --git a/packages/components/i18n/languages/index.ts b/packages/components/i18n/languages/index.ts
new file mode 100644
index 000000000..1f55b25e6
--- /dev/null
+++ b/packages/components/i18n/languages/index.ts
@@ -0,0 +1,4 @@
+export * from './en_US'
+export * from './es_ES'
+export * from './zh_CN'
+export * from './zh_TW'
diff --git a/packages/components/i18n/languages/zh_CN.ts b/packages/components/i18n/languages/zh_CN.ts
new file mode 100755
index 000000000..1185dfde9
--- /dev/null
+++ b/packages/components/i18n/languages/zh_CN.ts
@@ -0,0 +1,9 @@
+import { Locale } from '../types'
+
+// eslint-disable-next-line camelcase
+export const zh_CN: Locale = {
+ type: 'zh-CN',
+ global: {
+ placeholder: '请选择',
+ },
+}
diff --git a/packages/components/i18n/languages/zh_TW.ts b/packages/components/i18n/languages/zh_TW.ts
new file mode 100755
index 000000000..b5ae50bf2
--- /dev/null
+++ b/packages/components/i18n/languages/zh_TW.ts
@@ -0,0 +1,9 @@
+import { Locale } from '../types'
+
+// eslint-disable-next-line camelcase
+export const zh_TW: Locale = {
+ type: 'zh-TW',
+ global: {
+ placeholder: '請選擇',
+ },
+}
diff --git a/packages/components/i18n/types.ts b/packages/components/i18n/types.ts
new file mode 100644
index 000000000..2b4a7c404
--- /dev/null
+++ b/packages/components/i18n/types.ts
@@ -0,0 +1,12 @@
+export interface GlobalLocale {
+ placeholder: string
+}
+
+export interface Locale {
+ type: LocaleType
+ global: GlobalLocale
+}
+
+export type LocaleKey = keyof Locale
+
+export type LocaleType = 'en_US' | 'es_ES' | 'zh-CN' | 'zh-TW'
diff --git a/packages/components/i18n/useI18n.ts b/packages/components/i18n/useI18n.ts
new file mode 100644
index 000000000..6ffb80692
--- /dev/null
+++ b/packages/components/i18n/useI18n.ts
@@ -0,0 +1,48 @@
+import { computed, ref } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
+import { IDUX_COMPONENTS_PREFIX } from '../core/constant'
+import { Logger } from '../core/logger'
+import { zh_CN as defaultLocale } from './languages/zh_CN'
+import type { Locale, LocaleKey, LocaleType } from './types'
+
+const currentType: Ref = ref('zh-CN')
+const localeMap: Partial> = { 'zh-CN': defaultLocale }
+
+export function useI18n(locale: LocaleType | Locale): void {
+ if (typeof locale === 'string') {
+ if (localeMap[locale]) {
+ currentType.value = locale
+ } else {
+ Logger.warn(`${IDUX_COMPONENTS_PREFIX} The local [${locale}] was not added, please via 'addI18n()' add it.`)
+ }
+ } else {
+ const type = locale.type
+ localeMap[type] = locale
+ currentType.value = type
+ }
+}
+
+export function addI18n(locale: Locale | Locale[]): void {
+ const locales = Array.isArray(locale) ? locale : [locale]
+ locales.forEach(item => {
+ localeMap[item.type] = item
+ })
+}
+
+/**
+ *
+ * @param key optional, gets the value of current locale by key
+ */
+export function getI18n(key: T): ComputedRef
+export function getI18n(): ComputedRef
+export function getI18n(key?: T): ComputedRef {
+ return computed(() => {
+ let currLocale = localeMap[currentType.value]
+ /* istanbul ignore next */
+ if (!currLocale) {
+ /* istanbul ignore next */
+ currLocale = defaultLocale
+ }
+ return key ? currLocale[key] : currLocale
+ })
+}
diff --git a/packages/components/icon/__tests__/__snapshots__/icon.spec.ts.snap b/packages/components/icon/__tests__/__snapshots__/icon.spec.ts.snap
index 2a24fc2b1..2bf94e8f2 100644
--- a/packages/components/icon/__tests__/__snapshots__/icon.spec.ts.snap
+++ b/packages/components/icon/__tests__/__snapshots__/icon.spec.ts.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Icon.vue render work 1`] = `""`;
+exports[`Icon.vue render work 1`] = `""`;
diff --git a/packages/components/icon/src/utils.ts b/packages/components/icon/src/utils.ts
index 3c5409169..0e6dca5cd 100644
--- a/packages/components/icon/src/utils.ts
+++ b/packages/components/icon/src/utils.ts
@@ -104,7 +104,7 @@ export function createScriptElements(urls: string[], index = 0): void {
if (urls.length > index + 1) {
scriptElement.onload = () => createScriptElements(urls, index + 1)
scriptElement.onerror = () => {
- Logger.warn(`The url ${currentUrl} failed to load`)
+ Logger.error(`The url ${currentUrl} failed to load`)
createScriptElements(urls, index + 1)
}
}
diff --git a/packages/components/index.ts b/packages/components/index.ts
index 366482b77..2540fe0e3 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -21,4 +21,5 @@ export * from './core/config'
export * from './core/types'
export * from './button'
+export * from './i18n'
export * from './icon'