目前支持语言
| 最低支持版本 | 语言 |
|---|---|
| v0.0.1 | 简体中文: zh_CN |
| v0.7.0 | 英语: en_GB、日语: ja_JP、韩语: ko_KR |
| v1.8.0 | 阿拉伯语: ar |
| v1.11.0 | 越南语: vi_VN、俄罗斯语: ru_RU、印尼语: id_ID、马来语: ms_MY、泰语: th_TH、土耳其语: tr_TR |
| v1.17.0 | 葡萄牙语(巴西): pt_BR |
| v1.28.0 | 繁体中文: zh_TW |
| v2.2.0 | 西班牙语: es |
| v2.15.0 | 意大利语: it、法语:fr、德语:de |
| v2.21.0 | 罗马尼亚语: ro |
| v2.29.0 | 瑞典语: sv_SE、波兰语: pl_PL、荷兰语: nl_NL |
| v2.88.0 | 阿塞拜疆语:az、保加利亚语:bg、加泰罗尼亚语:ca、捷克语:cs_CZ、宿务语:ceb_PH、丹麦语: da、希腊语:el_GR、西班牙语(拉美):es_419、爱沙尼亚语:et、波斯语:fa_IR、菲律宾语:fil_PH、芬兰语: fi_FI、法语(加):fr_CA、爱尔兰语:ga、希伯来语:he_IL、印地语:hi_IN、克罗地亚语:hr、匈牙利语:hu_HU、冰岛语:is、爪哇语:jv_ID、哈萨克语:kk、高棉语:km_KH、立陶宛语:lt、拉脱维亚语:lv、缅甸语:my_MM、挪威语: nb、葡萄牙语:pt、斯洛伐克语:sk、斯洛文尼亚语:sl、阿尔巴尼亚语:sq、斯瓦希里语:sw、乌克兰语:uk_UA、乌尔都语:ur、乌兹别克语:uz |
已支持组件
目前有以下组件存在内置默认文本,均已实现国际化多语言适配
Calendar、Cascader、Chat、DatePicker、Form、Image、List、Modal、Navigation、Nav、Pagination、Popconfirm、Select、Table、TimePicker、Transfer、Tree、TreeSelect、Typography、Upload
使用
LocaleProvider 通过 provide/inject 向后代传递语言包,你只需要在应用外围包裹一次即可全局生效
当需要切换语言时,直接切换 locale 属性的值即可
<script setup lang="ts">
import en_GB from '@fibrous/semi-foundation/locale/source/en_GB';
import { LocaleProvider } from '@fibrous/semi-ui-vue';
</script>
<template>
<LocaleProvider :locale="en_GB">
<App />
</LocaleProvider>
</template>
不传 locale 时 LocaleProvider 继承外层 LocaleProvider 的语言,最外层都没有时回退 zh_CN;ConfigProvider 的 locale 优先于 LocaleProvider。
代码示例
国际化
- Total pages: 10
- Items per page: 10
- 合計ページ数:10
- 1ページあたりのアイテム数:10
代码
<script setup lang="ts">
import en_GB from '@fibrous/semi-foundation/locale/source/en_GB';
import ja_JP from '@fibrous/semi-foundation/locale/source/ja_JP';
import { LocaleProvider, Pagination } from '@fibrous/semi-ui-vue';
</script>
<template>
<LocaleProvider :locale="en_GB">
<Pagination showSizeChanger showTotal :style="{ margin: '20px' }" :total="100" />
</LocaleProvider>
<LocaleProvider :locale="ja_JP">
<Pagination showSizeChanger showTotal :style="{ margin: '20px' }" :total="100" />
</LocaleProvider>
</template>自定义国际化组件
当你的自定义组件,也希望消费 Semi LocaleProvider 中的 localeCode 或者读取具体某个组件的 i18n 文本时,你可以使用 LocaleConsumer 的默认作用域插槽获取 locale、localeCode、dateFnsLocale 与 currency;在 <script setup> 中可以调用 useLocale(componentName),它返回包含同样四项的 computed。语言的优先级为 ConfigProvider、LocaleProvider、zh_CN。自定义命名空间(示例中的 ComponentA)通过 declaration merging 扩展 Locale 类型。
代码
<script lang="ts">
// Custom component text joins the Locale type through declaration merging
declare module '@fibrous/semi-foundation/locale/interface' {
export interface Locale {
ComponentA: { customKey: string };
}
}
</script>
<script setup lang="ts">
import en_GB from '@fibrous/semi-foundation/locale/source/en_GB';
import ko_KR from '@fibrous/semi-foundation/locale/source/ko_KR';
import zh_CN from '@fibrous/semi-foundation/locale/source/zh_CN';
import { LocaleConsumer, LocaleProvider } from '@fibrous/semi-ui-vue';
const locales = [
{ ...zh_CN, ComponentA: { customKey: 'semi' } },
{ ...ko_KR, ComponentA: { customKey: 'design' } },
{ ...en_GB, ComponentA: { customKey: 'dsm' } },
];
</script>
<template>
<LocaleProvider v-for="locale in locales" :key="`semi-${locale.code}`" :locale="locale">
<LocaleConsumer componentName="TimePicker">
<template #default="{ locale: timePicker, localeCode }">
<div>{{ localeCode }} : {{ timePicker.begin }}</div>
</template>
</LocaleConsumer>
</LocaleProvider>
<LocaleProvider v-for="locale in locales" :key="`custom-${locale.code}`" :locale="locale">
<LocaleConsumer componentName="ComponentA">
<template #default="{ locale: componentA }">
<div>{{ componentA.customKey }}</div>
</template>
</LocaleConsumer>
</LocaleProvider>
</template>支持多语言的组件
示例给出了目前所有支持多语言的组件
当你的网站有RTL适配需求时,推荐直接使用ConfigProvider,除了可配置locale外,还可以直接同时配置direction='rtl'/'ltr'
若无RTL适配需求,直接使用LocaleProvider即可
Pagination
- 总页数:10
- 每页条数:10
- 跳至页
Modal
Select & Cascader
DatePicker
TimePicker
TreeSelect
Table
| Name | Age | Address |
|---|
| Bytedance 0 | 32 | Beijing, Haidian. Zhichun Road 0 |
| Bytedance 1 | 32 | Beijing, Haidian. Zhichun Road 1 |
| Bytedance 2 | 32 | Beijing, Haidian. Zhichun Road 2 |
| Bytedance 3 | 32 | Beijing, Haidian. Zhichun Road 3 |
| Bytedance 4 | 32 | Beijing, Haidian. Zhichun Road 4 |
| Bytedance 5 | 32 | Beijing, Haidian. Zhichun Road 5 |
| Bytedance 6 | 32 | Beijing, Haidian. Zhichun Road 6 |
| Bytedance 7 | 32 | Beijing, Haidian. Zhichun Road 7 |
| Bytedance 8 | 32 | Beijing, Haidian. Zhichun Road 8 |
| Bytedance 9 | 32 | Beijing, Haidian. Zhichun Road 9 |
Table - Empty
| Name | Age | Address |
|---|
List - Empty
Calendar
Typography - Copyable
Click to copy text.
Typography - Collapsible
支持展开和折叠:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
Transfer
Image




Form
Navigation
代码
<script setup lang="ts">
import { shallowRef } from 'vue';
import zh_CN from '@fibrous/semi-foundation/locale/source/zh_CN';
import { IconSemiLogo, IconStar, IconUser } from '@fibrous/semi-icons-vue';
import { Button, Calendar, Cascader, ConfigProvider, DatePicker, Form, FormInput, Image, ImagePreview, List, LocaleProvider, Modal, Nav, Pagination, Select, Table, TimePicker, Transfer, TreeSelect, Typography, type NavItemConfig, type NavItems } from '@fibrous/semi-ui-vue';
const { Paragraph } = Typography;
const treeData = [
{
label: 'Asia',
value: 'asia',
key: '1',
children: [
{
label: 'China',
value: 'china',
key: '1-0',
children: [
{ label: 'Beijing', value: 'beijing', key: '1-0-0' },
{ label: 'Shanghai', value: 'shanghai', key: '1-0-1' },
],
},
{
label: 'Japan',
value: 'japan',
key: '1-1',
children: [{ label: 'Osaka', value: 'osaka', key: '1-1-0' }],
},
],
},
];
const columns = [
{ title: 'Name', width: 250, dataIndex: 'name' },
{ title: 'Age', width: 150, dataIndex: 'age' },
{ title: 'Address', dataIndex: 'address' },
];
const dataSource = Array.from({ length: 46 }, (_, i) => ({
key: String(i),
name: `Bytedance ${i}`,
age: 32,
address: `Beijing, Haidian. Zhichun Road ${i}`,
}));
const transferData = Array.from({ length: 100 }, (_, i) => ({
label: `选项名称 ${i}`,
value: i,
disabled: false,
key: i,
}));
const srcList = ['/assets/root-web-sites/abstract.jpg', '/assets/root-web-sites/sky.jpg', '/assets/root-web-sites/greenleaf.jpg', '/assets/root-web-sites/colorful.jpg'];
const navItems: NavItems = [
{ itemKey: 'user', text: '用户管理' },
{ itemKey: 'union', text: '活动管理' },
];
const navIcons = { user: IconUser, union: IconStar };
const navIconOf = (item: NavItemConfig) => navIcons[item.itemKey as keyof typeof navIcons];
// A pack loads when it is picked, so the page does not bundle every language up front
const loaders = {
ar: () => import('@fibrous/semi-foundation/locale/source/ar'),
az: () => import('@fibrous/semi-foundation/locale/source/az'),
bg: () => import('@fibrous/semi-foundation/locale/source/bg'),
bn_IN: () => import('@fibrous/semi-foundation/locale/source/bn_IN'),
ca: () => import('@fibrous/semi-foundation/locale/source/ca'),
ceb_PH: () => import('@fibrous/semi-foundation/locale/source/ceb_PH'),
cs_CZ: () => import('@fibrous/semi-foundation/locale/source/cs_CZ'),
da: () => import('@fibrous/semi-foundation/locale/source/da'),
de: () => import('@fibrous/semi-foundation/locale/source/de'),
el_GR: () => import('@fibrous/semi-foundation/locale/source/el_GR'),
en_GB: () => import('@fibrous/semi-foundation/locale/source/en_GB'),
en_US: () => import('@fibrous/semi-foundation/locale/source/en_US'),
es: () => import('@fibrous/semi-foundation/locale/source/es'),
es_419: () => import('@fibrous/semi-foundation/locale/source/es_419'),
et: () => import('@fibrous/semi-foundation/locale/source/et'),
fa_IR: () => import('@fibrous/semi-foundation/locale/source/fa_IR'),
fi_FI: () => import('@fibrous/semi-foundation/locale/source/fi_FI'),
fil_PH: () => import('@fibrous/semi-foundation/locale/source/fil_PH'),
fr: () => import('@fibrous/semi-foundation/locale/source/fr'),
fr_CA: () => import('@fibrous/semi-foundation/locale/source/fr_CA'),
ga: () => import('@fibrous/semi-foundation/locale/source/ga'),
he_IL: () => import('@fibrous/semi-foundation/locale/source/he_IL'),
hi_IN: () => import('@fibrous/semi-foundation/locale/source/hi_IN'),
hr: () => import('@fibrous/semi-foundation/locale/source/hr'),
hu_HU: () => import('@fibrous/semi-foundation/locale/source/hu_HU'),
id_ID: () => import('@fibrous/semi-foundation/locale/source/id_ID'),
is: () => import('@fibrous/semi-foundation/locale/source/is'),
it: () => import('@fibrous/semi-foundation/locale/source/it'),
ja_JP: () => import('@fibrous/semi-foundation/locale/source/ja_JP'),
jv_ID: () => import('@fibrous/semi-foundation/locale/source/jv_ID'),
kk: () => import('@fibrous/semi-foundation/locale/source/kk'),
km_KH: () => import('@fibrous/semi-foundation/locale/source/km_KH'),
ko_KR: () => import('@fibrous/semi-foundation/locale/source/ko_KR'),
lt: () => import('@fibrous/semi-foundation/locale/source/lt'),
lv: () => import('@fibrous/semi-foundation/locale/source/lv'),
ms_MY: () => import('@fibrous/semi-foundation/locale/source/ms_MY'),
my_MM: () => import('@fibrous/semi-foundation/locale/source/my_MM'),
nb: () => import('@fibrous/semi-foundation/locale/source/nb'),
nl_NL: () => import('@fibrous/semi-foundation/locale/source/nl_NL'),
pl_PL: () => import('@fibrous/semi-foundation/locale/source/pl_PL'),
pt: () => import('@fibrous/semi-foundation/locale/source/pt'),
pt_BR: () => import('@fibrous/semi-foundation/locale/source/pt_BR'),
ro: () => import('@fibrous/semi-foundation/locale/source/ro'),
ru_RU: () => import('@fibrous/semi-foundation/locale/source/ru_RU'),
sk: () => import('@fibrous/semi-foundation/locale/source/sk'),
sl: () => import('@fibrous/semi-foundation/locale/source/sl'),
sq: () => import('@fibrous/semi-foundation/locale/source/sq'),
sv_SE: () => import('@fibrous/semi-foundation/locale/source/sv_SE'),
sw: () => import('@fibrous/semi-foundation/locale/source/sw'),
th_TH: () => import('@fibrous/semi-foundation/locale/source/th_TH'),
tr_TR: () => import('@fibrous/semi-foundation/locale/source/tr_TR'),
uk_UA: () => import('@fibrous/semi-foundation/locale/source/uk_UA'),
ur: () => import('@fibrous/semi-foundation/locale/source/ur'),
uz: () => import('@fibrous/semi-foundation/locale/source/uz'),
vi_VN: () => import('@fibrous/semi-foundation/locale/source/vi_VN'),
zh_TW: () => import('@fibrous/semi-foundation/locale/source/zh_TW'),
};
type LocaleCode = 'zh_CN' | keyof typeof loaders;
const languageOptions: { value: LocaleCode; label: string }[] = [
{ value: 'zh_CN', label: '简体中文' },
{ value: 'en_US', label: '英语(美)' },
{ value: 'en_GB', label: '英语(英)' },
{ value: 'ja_JP', label: '日语' },
{ value: 'ko_KR', label: '韩语' },
{ value: 'ar', label: '阿拉伯语' },
{ value: 'vi_VN', label: '越南语' },
{ value: 'ru_RU', label: '俄罗斯语' },
{ value: 'id_ID', label: '印尼语' },
{ value: 'ms_MY', label: '马来语' },
{ value: 'th_TH', label: '泰语' },
{ value: 'tr_TR', label: '土耳其语' },
{ value: 'pt_BR', label: '葡萄牙语(巴西)' },
{ value: 'zh_TW', label: '繁体中文' },
{ value: 'es', label: '西班牙语' },
{ value: 'de', label: '德语' },
{ value: 'it', label: '意大利语' },
{ value: 'fr', label: '法语' },
{ value: 'ro', label: '罗马尼亚语' },
{ value: 'sv_SE', label: '瑞典语' },
{ value: 'pl_PL', label: '波兰语' },
{ value: 'nl_NL', label: '荷兰语' },
{ value: 'az', label: '阿塞拜疆语' },
{ value: 'bn_IN', label: '孟加拉语' },
{ value: 'bg', label: '保加利亚语' },
{ value: 'ca', label: '加泰罗尼亚语' },
{ value: 'cs_CZ', label: '捷克语' },
{ value: 'ceb_PH', label: '宿务语' },
{ value: 'da', label: '丹麦语' },
{ value: 'el_GR', label: '希腊语' },
{ value: 'es_419', label: '西班牙语(拉美)' },
{ value: 'et', label: '爱沙尼亚语' },
{ value: 'fa_IR', label: '波斯语' },
{ value: 'fil_PH', label: '菲律宾语' },
{ value: 'fi_FI', label: '芬兰语' },
{ value: 'fr_CA', label: '法语(加)' },
{ value: 'ga', label: '爱尔兰语' },
{ value: 'he_IL', label: '希伯来语' },
{ value: 'hi_IN', label: '印地语' },
{ value: 'hr', label: '克罗地亚语' },
{ value: 'hu_HU', label: '匈牙利语' },
{ value: 'is', label: '冰岛语' },
{ value: 'jv_ID', label: '爪哇语' },
{ value: 'kk', label: '哈萨克语' },
{ value: 'km_KH', label: '高棉语' },
{ value: 'lt', label: '立陶宛语' },
{ value: 'lv', label: '拉脱维亚语' },
{ value: 'my_MM', label: '缅甸语' },
{ value: 'nb', label: '挪威语' },
{ value: 'pt', label: '葡萄牙语' },
{ value: 'sk', label: '斯洛伐克语' },
{ value: 'sl', label: '斯洛文尼亚语' },
{ value: 'sq', label: '阿尔巴尼亚语' },
{ value: 'sw', label: '斯瓦希里语' },
{ value: 'uk_UA', label: '乌克兰语' },
{ value: 'ur', label: '乌尔都语' },
{ value: 'uz', label: '乌兹别克语' },
];
const localeCode = shallowRef<LocaleCode>('zh_CN');
const locale = shallowRef(zh_CN);
let latest: LocaleCode = 'zh_CN';
const onLanguageChange = async (code: LocaleCode) => {
latest = code;
const pack = code === 'zh_CN' ? zh_CN : (await loaders[code]()).default;
// A pick made while this pack was loading wins
if (latest === code) {
localeCode.value = code;
locale.value = pack;
}
};
const modalVisible = shallowRef(false);
const closeModal = () => {
modalVisible.value = false;
};
</script>
<template>
<div>
<div :style="{ borderBottom: '1px solid var(--semi-color-border)', paddingBottom: '20px' }">
<Select aria-label="切换语言" defaultValue="zh_CN" :style="{ width: '250px' }" @change="(value) => onLanguageChange(value as LocaleCode)">
<template #prefix>切换语言</template>
<Select.Option v-for="option in languageOptions" :key="option.value" :value="option.value">
{{ option.label }}
</Select.Option>
</Select>
</div>
<LocaleProvider :locale="locale">
<ConfigProvider :direction="localeCode === 'ar' ? 'rtl' : 'ltr'" :locale="locale">
<div :key="localeCode">
<h5>Pagination</h5>
<Pagination showQuickJumper showSizeChanger showTotal :style="{ margin: '10px' }" :total="100" />
<h5>Modal</h5>
<div :style="{ margin: '10px' }">
<Button @click="modalVisible = true">Show Modal</Button>
<Modal v-model:visible="modalVisible" title="Modal" @cancel="closeModal" @ok="closeModal">
<p>This is the content of a basic modal.</p>
<p>More content...</p>
</Modal>
</div>
<h5>Select & Cascader</h5>
<div :style="{ margin: '10px' }">
<Select aria-label="Select" filter :style="{ width: '180px' }">
<Select.Option value="abc">abc</Select.Option>
<Select.Option disabled value="vigo">vigo</Select.Option>
<Select.Option value="hotsoon">hotsoon</Select.Option>
</Select>
<Cascader filterTreeNode :style="{ width: '300px', margin: '10px' }" :treeData="treeData">
<template #prefix>Cascader</template>
</Cascader>
</div>
<h5>DatePicker</h5>
<DatePicker :style="{ margin: '10px', width: '250px' }" />
<DatePicker :style="{ margin: '10px', width: '300px' }" type="dateTime" />
<DatePicker :style="{ margin: '10px', width: '300px' }" type="dateRange" />
<DatePicker :style="{ margin: '10px', width: '450px' }" type="dateTimeRange" />
<h5>TimePicker</h5>
<TimePicker :style="{ margin: '10px' }" />
<TimePicker :style="{ margin: '10px' }" use12Hours />
<br />
<br />
<h5>TreeSelect</h5>
<TreeSelect :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }" filterTreeNode :style="{ margin: '10px', width: '300px' }" :treeData="treeData" />
<h5>Table</h5>
<Table :columns="columns" :dataSource="dataSource" :scroll="{ y: 320 }" />
<h5>Table - Empty</h5>
<Table :columns="columns" :dataSource="[]" :scroll="{ y: 320 }" />
<h5>List - Empty</h5>
<List :dataSource="[]">
<template #header>
<div>List</div>
</template>
</List>
<h5>Calendar</h5>
<Calendar :displayValue="new Date(2024, 4, 6, 12)" mode="month" />
<h5>Typography - Copyable</h5>
<Paragraph copyable>Click to copy text.</Paragraph>
<h5>Typography - Collapsible</h5>
<Paragraph :ellipsis="{ rows: 3, expandable: true, collapsible: true }" :style="{ width: '300px' }">
支持展开和折叠:Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。
</Paragraph>
<h5>Transfer</h5>
<Transfer :dataSource="transferData" :style="{ width: '568px', height: '416px' }" />
<h5>Image</h5>
<ImagePreview showTooltip>
<Image v-for="(src, index) in srcList" :key="src" :alt="`lamp${index + 1}`" :src="src" :style="{ marginRight: '5px' }" :width="200" />
</ImagePreview>
<h5>Form</h5>
<Form layout="horizontal" @valueChange="(values) => console.log(values)">
<FormInput field="UserName" :label="{ text: '角色', optional: true }" :style="{ width: '200px' }" />
</Form>
<h5>Navigation</h5>
<Nav :bodyStyle="{ height: '320px' }" :footer="{ collapseButton: true }" :items="navItems">
<template #icon="{ item }">
<component :is="navIconOf(item)" />
</template>
<Nav.Header text="Semi 数据后台">
<template #logo>
<IconSemiLogo :style="{ height: '36px', fontSize: '36px' }" />
</template>
</Nav.Header>
</Nav>
</div>
</ConfigProvider>
</LocaleProvider>
</div>
</template>API 参考
LocaleProvider
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| locale | 语言包。不传、传 undefined 或 null 时继承外层 LocaleProvider 的语言,最外层都没有时组件回退 zh_CN;ConfigProvider 的 locale 优先于它。 | Locale | null | undefined |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| 无 |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| default | 使用该语言包的子树。 | any |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
LocaleProvider 不接受未声明的属性,class、style 等 attrs 不会落到 DOM 上。
LocaleConsumer
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| componentName | 要读取的语言包命名空间,如 TimePicker;只接受 Locale 中声明的组件名,自定义命名空间通过 declaration merging 扩展 Locale。 | K | undefined |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| 无 |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| default | 作用域插槽,参数为 locale(componentName 对应的文案片段)、localeCode、dateFnsLocale 与 currency。 | TypeLocaleResult<Locale[K]> |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
LocaleConsumer 不渲染自己的元素,也不接受未声明的属性。
useLocale
useLocale(componentName) 返回 ComputedRef<LocaleResult>,componentName 可以是字符串,也可以是 ref 或 getter。
| 字段 | 说明 | 类型 |
|---|---|---|
| locale | componentName 对应的文案片段 | Locale[K] |
| localeCode | 语言代码 | string |
| dateFnsLocale | date-fns 的语言对象,缺失时由 date-fns 使用默认的 enUS | Locale(date-fns) |
| currency | 语言所在地区的 ISO 4217 货币码 | string |