docs(i18n): 新增国际化文档(beta)

note: 由于时间关系,本次提交仅为初版,该文档尚未完成
This commit is contained in:
贤心 2025-06-20 17:47:49 +08:00
parent 0a0e643471
commit 39a55a1b6e
3 changed files with 735 additions and 0 deletions

481
docs/i18n/detail/demo.md Normal file
View File

@ -0,0 +1,481 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>i18n 演示 - Layui</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="{{= d.layui[2].cdn.css }}" rel="stylesheet">
</head>
<body class="layui-padding-3">
<div id="root"></div>{{!
<template id="template">
{{ const i18n = layui.i18n; }}
<div class="layui-form">
<div class="layui-inline">
<strong>{{= i18n.$t('custom.switchLanguage') }}: </strong>
</div>
<div class="layui-inline">
<select id="change-locale" lay-filter="change-locale">
<option value="zh-CN">简体中文</option>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="zh-HK">繁體中文</option>
</select>
</div>
</div>
<br>
<fieldset class="layui-elem-field">
<legend>README</legend>
<div class="layui-field-box layui-text" id="tpl-test">
<p>{{= i18n.$t('custom.readme.description') }}</p>
<ul>
<li><strong>locale</strong>: <span style="color:red">{{= i18n.config.locale }}</span></li>
<li><strong>Date</strong>: {{= new Date().toLocaleDateString(i18n.config.locale) }}</li>
<li><strong>Hello</strong>: {{= i18n.$t('custom.readme.hello') }}</li>
</ul>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>code</legend>
<div class="layui-field-box">
<pre id="demo-code" class="layui-code" lay-options="{}">
code content
</pre>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>colorpicker</legend>
<div class="layui-field-box">
<div id="demo-colorpicker"></div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>dropdown</legend>
<div class="layui-field-box">
<button id="demo-dropdown" class="layui-btn demo-dropdown-base">
<span>Dropdown</span>
<i class="layui-icon layui-icon-down layui-font-12"></i>
</button>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>flow</legend>
<div class="layui-field-box">
<div class="flow-demo" id="demo-flow"></div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>form</legend>
<div class="layui-field-box">
<form class="layui-form" action="">
<div class="layui-form-item">
<label class="layui-form-label">{{= i18n.$t('custom.form.required') }}</label>
<div class="layui-input-block">
<input type="text" name="username" lay-verify="required" lay-vertype="alert" placeholder="{{= i18n.$t('custom.form.placeholder') }}" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">{{= i18n.$t('custom.form.phone') }}</label>
<div class="layui-input-inline layui-input-wrap">
<input type="tel" name="phone" lay-verify="phone" autocomplete="off" value="123456" lay-affix="clear"
class="layui-input demo-phone">
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">{{= i18n.$t('custom.form.email') }}</label>
<div class="layui-input-inline">
<input type="text" name="email" value="123.com" lay-verify="email" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">{{= i18n.$t('custom.form.date') }}</label>
<div class="layui-input-inline layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-date"></i>
</div>
<input type="text" name="date" value="2077" id="date" lay-verify="date" placeholder="yyyy-MM-dd"
autocomplete="off" class="layui-input">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">{{= i18n.$t('custom.form.select') }}</label>
<div class="layui-input-block">
<select name="interest" lay-filter="aihao" lay-search>
<option value=""></option>
<option value="0">AAA</option>
<option value="1" selected>BBB</option>
<option value="2">CCC</option>
<option value="3">DDD</option>
<option value="4">EEE</option>
</select>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="layui-btn" lay-submit lay-filter="demo1">
{{= i18n.$t('custom.form.submit') }}
</button>
<button type="reset" class="layui-btn layui-btn-primary">
{{= i18n.$t('custom.form.reset') }}
</button>
</div>
</div>
</form>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>laydate</legend>
<div class="layui-field-box">
<div class="layui-inline">
<input class="layui-input" id="demo-laydate" />
</div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>layer</legend>
<div class="layui-field-box">
<button type="button" class="layui-btn layui-btn-primary" lay-on="alert">Alert</button>
<button type="button" class="layui-btn layui-btn-primary" lay-on="prompt">Prompt</button>
<button type="button" class="layui-btn layui-btn-primary" lay-on="photos">Photos</button>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>laypage</legend>
<div class="layui-field-box">
<div id="demo-laypage-all"></div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>table</legend>
<div class="layui-field-box">
<table class="layui-hide" id="demo-table" lay-filter="test"></table>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>transfer</legend>
<div class="layui-field-box">
<div id="demo-transfer"></div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>tree</legend>
<div class="layui-field-box">
<div id="demo-tree"></div>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>upload</legend>
<div class="layui-field-box">
<button type="button" class="layui-btn" id="demo-upload">
<i class="layui-icon layui-icon-upload"></i> Upload
</button>
</div>
</fieldset>
<fieldset class="layui-elem-field">
<legend>utils</legend>
<div class="layui-field-box">
<label>
timeAgo: <input id="demo-time-ago-picker" type="datetime-local" /> <span id="demo-time-ago-display"></span>
</label>
<br>
<label>
toDateString: <div id="demo-toDateString"></div>
</label>
</div>
</fieldset>
</template>!}}
<script src="{{= d.layui[2].cdn.js }}"></script>
<script>
layui.use(async function () {
const {
$, colorpicker, dropdown, flow, form,
i18n, laydate, laypage, laytpl, layer,
table, transfer, tree, upload, util
} = layui;
// 演示异步加载组件语言包
const base = 'https://cdn.jsdelivr.net/gh/layui/layui@0a0e64347166796cf42ac6a8ee08b27082f36c02/examples/i18n/';
const { default: en } = await import(`${base}en.js`);
const { default: fr } = await import(`${base}fr.js`);
const { default: zhHK } = await import(`${base}zh-HK.js`);
// 组件 i18n 配置
i18n.set({
locale: layui.data('layui-i18n-test').locale || 'zh-CN', // 指定语言
messages: {
en, // 扩展英语
fr, // 扩展法语
'zh-HK': zhHK, // 扩展繁体中文
},
});
// 业务 i18n 配置
i18n.set({
messages: {
'zh-CN': {
custom: {
switchLanguage: '切换语言',
readme: {
description: 'layui.i18n.$t 是私有方法(未文档化),此处仅用于演示',
hello: '你好',
},
form: {
required: '验证必填项',
phone: '验证手机号',
email: '验证邮箱',
date: '验证日期',
select: '选择框',
submit: '立即提交',
reset: '重置',
placeholder: '请输入'
}
}
},
'en': {
custom: {
switchLanguage: 'Switch Language',
readme: {
description: 'layui.i18n.$t is a private method (undocumented), used here for demonstration only.',
hello: 'Hello',
},
form: {
required: 'Required',
phone: 'Telephone',
email: 'Email',
date: 'Date',
select: 'Select',
submit: 'Submit',
reset: 'Reset',
placeholder: 'Please enter'
}
}
},
'fr': {
custom: {
switchLanguage: 'Changer la langue',
readme: {
description: 'layui.i18n.$t est une méthode privée (non documentée), utilisée ici uniquement à des fins de démonstration.',
hello: 'Bonjour',
},
form: {
required: 'Requis',
phone: 'Téléphone',
email: 'Email',
date: 'Date',
select: 'Sélection',
submit: 'Soumettre maintenant',
reset: 'Réinitialiser',
placeholder: 'Veuillez entrer'
}
}
},
'zh-HK': {
custom: {
switchLanguage: '切換語言',
readme: {
description: 'layui.i18n.$t 是私有方法(未文檔化),此處僅用於示範',
hello: '你好',
},
form: {
required: '驗證必填項',
phone: '驗證手機號',
email: '驗證郵箱',
date: '驗證日期',
select: '選擇框',
submit: '立即提交',
reset: '重置',
placeholder: '請輸入'
}
}
}
}
});
// 渲染页面模板
const template = $('#template').html();
const html = laytpl(template, { tagStyle: 'modern' }).render();
$('#root').html(html);
/**
* 组件示例
*/
// code
layui.code({
elem: "#demo-code",
preview: true,
tools: ['copy', 'full', 'window'],
header: true,
lang: 'html',
langMarker: true,
});
// colorpicker
colorpicker.render({
elem: "#demo-colorpicker",
});
// dropdown
dropdown.render({
elem: "#demo-dropdown",
});
// flow
flow.load({
elem: '#demo-flow',
scrollElem: '#demo-flow',
done: function (page, next) {
// 模拟数据插入
setTimeout(function () {
var lis = [];
for (var i = 0; i < 3; i++) {
lis.push('<li>' + ((page - 1) * 3 + i + 1) + '</li>')
}
next(lis.join(''), page < 2);
}, 200);
}
});
// form
form.on('submit(demo1)', function (data) {
var field = data.field;
// 显示填写结果,仅作演示用
layer.alert(JSON.stringify(field));
return false;
});
//laydate
laydate.render({
elem: "#demo-laydate",
type: "datetime",
calendar: true
});
// layer
util.on({
alert: function () {
layer.alert("Hello world");
},
prompt: function () {
layer.prompt({ formType: 1, maxlength: 3 }, function (value, index) {
layer.close(index);
});
},
photos: function () {
layer.photos({
photos: {
"title": "Photos Demo",
"start": 0,
"data": [
{
"alt": "layer",
"pid": 1,
"src": "https://unpkg.com/outeres@0.1.1/demo/layer.png",
},
{
"alt": "error",
"pid": 3,
"src": "错误提示演示",
},
{
"alt": "universe",
"pid": 5,
"src": "https://unpkg.com/outeres@0.1.1/demo/outer-space.jpg",
}
]
}
});
}
})
// laypage
laypage.render({
elem: "demo-laypage-all",
count: 100,
layout: ["count", "prev", "page", "next", "limit", "refresh", "skip"],
});
// table
table.render({
elem: '#demo-table',
cols: [[{ field: 'test', title: '1', sort: true }, { field: 'test2', title: '2', sort: true }]],
data: new Array(0),
toolbar: 'default',
defaultToolbar: ['filter', 'exports', 'print'],
height: 'full',
page: true,
text: {
// none: 'none'
}
});
// transfer
transfer.render({
elem: '#demo-transfer',
data: [
{ "value": "1", "title": "Item 1" },
{ "value": "2", "title": "Item 2" },
{ "value": "3", "title": "Item 3" },
],
showSearch: true
});
// tree
tree.render({
elem: '#demo-tree',
//data: [],
data: [{ title: 'Item 1', id: 1, children: [{ title: 'Item 1-1', id: 2 }] }],
edit: ['add', 'update', 'del']
});
// upload
upload.render({
elem: '#demo-upload',
url: '', // 实际使用时改成您自己的上传接口即可。
multiple: true,
accept: 'file',
//exts: 'zip',
number: 1,
//size: 60,
});
// util
$('#demo-time-ago-picker').on('change', function(){
$('#demo-time-ago-display').html(
util.timeAgo(this.value)
);
})
$('#demo-toDateString').html(
util.toDateString('2023-01-01 11:35:25', 'yyyy-MM-dd HH:mm:ss A')
+ '<br>'
+ util.toDateString('2023-01-01 18:35:25', 'yyyy-MM-dd HH:mm:ss A')
)
// 演示:切换语言
$("#change-locale").val(i18n.config.locale);
form.render('select').on("select(change-locale)", function (elem) {
// 记录语言,并重载页面(推荐)
layui.data('layui-i18n-test', {
key: 'locale',
value: elem.value
});
window.location.reload();
});
$("body").css("opacity", 1);
console.log(i18n.config)
});
</script>
</body>
</html>

187
docs/i18n/detail/options.md Normal file
View File

@ -0,0 +1,187 @@
```js
i18n.set({
locale: 'zh-CN', // 设置语言环境
messages: { // 语言包
'zh-CN': { // 简体中文(默认)
code: {
copy: '复制代码',
copied: '已复制',
copyError: '复制失败',
maximize: '最大化显示',
restore: '还原显示',
preview: '在新窗口预览'
},
colorpicker: {
clear: '清除',
confirm: '确定'
},
dropdown: {
noData: '暂无数据'
},
flow: {
loadMore: '加载更多',
noMore: '没有更多了'
},
form: {
select: {
noData: '暂无数据',
noMatch: '无匹配数据',
placeholder: '请选择'
},
validateMessages: {
required: '必填项不能为空',
phone: '手机号格式不正确',
email: '邮箱格式不正确',
url: '链接格式不正确',
number: '只能填写数字',
date: '日期格式不正确',
identity: '身份证号格式不正确'
},
verifyErrorPromptTitle: '提示'
},
laydate: {
month: ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'],
weeks: ['日', '一', '二', '三', '四', '五', '六'],
time: ['时', '分', '秒'],
selectDate: '选择日期',
selectTime: '选择时间',
startTime: '开始时间',
endTime: '结束时间',
tools: {
confirm: '确定',
clear: '清空',
now: '现在',
reset: '重置'
},
timeout: '结束时间不能早于开始时间<br>请重新选择',
invalidDate: '不在有效日期或时间范围内',
formatError: ['日期格式不合法<br>必须遵循下述格式:<br>', '<br>已为你重置'],
preview: '当前选中的结果'
},
layer: {
confirm: '确定',
cancel: '取消',
defaultTitle: '信息',
prompt: {
InputLengthPrompt: '最多输入 {length} 个字符'
},
photos: {
noData: '没有图片',
tools:{
rotate: '旋转',
scaleX: '水平变换',
zoomIn: '放大',
zoomOut: '缩小',
reset: '还原',
close: '关闭'
},
viewPicture: '查看原图',
urlError: {
prompt: '当前图片地址异常,<br>是否继续查看下一张?',
confirm: '下一张',
cancel: '不看了'
}
}
},
laypage: {
prev: '上一页',
next: '下一页',
first: '首页',
last: '尾页',
total: '共 {total} 条',
pagesize: '条/页',
goto: '到第',
page: '页',
confirm: '确定'
},
table: {
sort: {
asc: '升序',
desc: '降序'
},
noData: '无数据',
tools:{
filter: {
title: '筛选列'
},
export: {
title: '导出',
noDataPrompt: '当前表格无数据',
compatPrompt: '导出功能不支持 IE请用 Chrome 等高级浏览器导出',
csvText : '导出 CSV 文件'
},
print: {
title: '打印',
noDataPrompt: '当前表格无数据'
}
},
dataFormatError: '返回的数据不符合规范,正确的成功状态码应为:"{statusName}": {statusCode}',
xhrError: '请求异常,错误提示:{msg}'
},
transfer: {
noData: '无数据',
noMatch: '无匹配数据',
title: ['列表一', '列表二'],
searchPlaceholder: '关键词搜索'
},
tree: {
defaultNodeName: '未命名',
noData: '无数据',
deleteNodePrompt: '确认删除"{name}"节点吗?'
},
upload: {
fileType: {
file: '文件',
image: '图片',
video: '视频',
audio: '音频'
},
validateMessages: {
fileExtensionError: '选择的{fileType}中包含不支持的格式',
filesOverLengthLimit: '同时最多只能上传: {length} 个文件',
currentFilesLength: '您当前已经选择了: {length} 个文件',
fileOverSizeLimit: '文件大小不能超过 {size}'
},
chooseText: '{length} 个文件'
},
util: {
timeAgo: {
days: '{days} 天前',
hours: '{hours} 小时前',
minutes: '{minutes} 分钟前',
future: '未来',
justNow: '刚刚'
},
toDateString: {
meridiem: function(hours, minutes){
var hm = hours * 100 + minutes;
if (hm < 600) {
return '凌晨';
} else if (hm < 900) {
return '早上';
} else if (hm < 1100) {
return '上午';
} else if (hm < 1300) {
return '中午';
} else if (hm < 1800) {
return '下午';
}
return '晚上';
}
}
}
},
'en': { // 通用英语
code: {
copy: 'Copy Code',
copied: 'Copied',
// ……
},
// ……
},
'fr': {}, // 通用法语
'zh-HK': {}, // 繁体中文
// …… // 更多语言代码
}
});
```

67
docs/i18n/index.md Normal file
View File

@ -0,0 +1,67 @@
---
title: 国际化 i18n
toc: true
---
# 国际化 <sup>2.12+</sup>
> `i18n` 是 2.12 版本新增的国际化模块,用于实现各组件提示文本的多语言支持。
<h2 id="examples" lay-toc="{anchor: null}" style="margin-bottom: 0;">示例</h2>
<div class="ws-docs-showcase"></div>
<pre class="layui-code" lay-options="{preview: 'iframe', style: 'height: 560px;', layout: ['preview', 'code'], tools: ['full','window']}">
<textarea>
{{- d.include("/i18n/detail/demo.md") }}
</textarea>
</pre>
<h2 id="api" lay-toc="{hot: true}">API</h2>
| API | 描述 |
| --- | --- |
| var i18n = layui.i18n | 获得 `i18n` 模块。|
| [i18n.set(options)](#set) | 设置语言包及默认语言。|
| [i18n.$t(options)](#translation) | 从国际化消息中获取翻译后的内容。 |
<h3 id="set" lay-toc="{level: 2}">国际化配置</h3>
i18n 支持两种配置方式,您可以根据实际场景选择任一方式。
#### 1. 通过 `i18n.set()` 方法配置
`i18n.set(options)`
- 参数 `options` : 基础属性选项。[#详见选项](#options)
#### 2. 通过 `LAYUI_GLOBAL.i18n` 全局配置
该方式通常用于 Layui 内置组件的自动渲染时的场景,需放置在 `layui.js` 导入之前,以确保国际化配置在组件渲染之前完成。
```html
<script>
window.LAYUI_GLOBAL = {
// 选项同 i18n.set(options)
i18n: {
locale: 'zh-CN',
messages: {
'en': {},
'zh-HK': {},
}
}
};
</script>
<script src="/layui/layui.js"></script>
```
<h3 id="options" lay-toc="{level: 2}">选项</h3>
<div>
{{- d.include("/i18n/detail/options.md") }}
</div>
## 💖 心语