update
This commit is contained in:
parent
5fb36e4a45
commit
b9eff3ea5b
@ -2,7 +2,7 @@
|
|||||||
"name": "voerka-i18n",
|
"name": "voerka-i18n",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"extract": ""
|
"extract": ""
|
||||||
|
54
readme.md
54
readme.md
@ -113,3 +113,57 @@ t("第{}章",7) // == Chapter Seven
|
|||||||
t("第{}章",100) // == Chapter 100
|
t("第{}章",100) // == Chapter 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 插值变量格式化
|
||||||
|
|
||||||
|
voerka-i18n支持对插值变量进行格式化
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
new VoerkaI18n({
|
||||||
|
formats:{
|
||||||
|
Date:{ // 日期格式
|
||||||
|
en:{
|
||||||
|
default:(value)=>dayjs(value).format("YYYY/MM/DD"),
|
||||||
|
time:(value)=>dayjs(value).format("HH:hh:mm"),
|
||||||
|
// 可以定义多种自定义格式...
|
||||||
|
},
|
||||||
|
cn:{
|
||||||
|
default:(value)=>dayjs(value).format("YYYY年MM月DD日"),
|
||||||
|
time:(value)=>dayjs(value).format("HH:hh:mm"),
|
||||||
|
// 可以定义多种自定义格式...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
String:{
|
||||||
|
en:{
|
||||||
|
firstUpper:(value)=>value[0].toUpperCase()+value.substr(1) // 首字母大写
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
以上代码定义了:
|
||||||
|
- `Date`类型的英文和中文的两个格式化函数
|
||||||
|
- `String`类型变量的`firstUpper`格式化函数
|
||||||
|
|
||||||
|
接下来,在翻译内容中使用。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
// languages/translates/default.json
|
||||||
|
{
|
||||||
|
"今天是{date}":{
|
||||||
|
en:"Today is {date}", // 使用默认格式
|
||||||
|
},
|
||||||
|
"现在是北京时间:{date}":{
|
||||||
|
en:"Now is {date.time}"
|
||||||
|
},
|
||||||
|
"":{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t("今天是{date}",{date:new Date()})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 将extract插件扫描的文件编译为语言文件
|
||||||
|
*
|
||||||
|
* 编译后的语言文件用于运行环境使用
|
||||||
|
*
|
||||||
|
* 编译原理如下:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} opts
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizeCompileOptions(opts={}) {
|
||||||
|
let options = Object.assign({
|
||||||
|
input:null, // 指定要编译的文件夹,即extract输出的语言文件夹
|
||||||
|
output:null, // 指定编译后的语言文件夹,如果没有指定,则使用input目录
|
||||||
|
formatters:{}, // 对插值变量进行格式化的函数清单
|
||||||
|
}, opts)
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function compile(opts={}){
|
||||||
|
|
||||||
|
}
|
@ -10,8 +10,8 @@ const deepmerge = require("deepmerge")
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const readJson = require("readjson")
|
const readJson = require("readjson")
|
||||||
const createLogger = require("logsets")
|
const createLogger = require("logsets")
|
||||||
|
const { replaceInterpolateVars,getDataTypeName } = require("./utils")
|
||||||
const logger = createLogger()
|
const logger = createLogger()
|
||||||
|
|
||||||
// 捕获翻译文本的默认正则表达式
|
// 捕获翻译文本的默认正则表达式
|
||||||
@ -190,7 +190,7 @@ function normalizeLanguageOptions(options){
|
|||||||
// 以下变量会被用来传递给提取器正则表达式
|
// 以下变量会被用来传递给提取器正则表达式
|
||||||
translation : {
|
translation : {
|
||||||
funcName : "t", // 翻译函数名称
|
funcName : "t", // 翻译函数名称
|
||||||
attrName :"data-i18n", // 用在html组件上的翻译属性名称
|
attrName :"data-i18n", // 用在html组件上的翻译属性名称
|
||||||
}
|
}
|
||||||
},options)
|
},options)
|
||||||
// 输出配置
|
// 输出配置
|
||||||
@ -270,8 +270,7 @@ function normalizeLanguageOptions(options){
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
logger.log("Supported languages\t: {}",options.languages.map(item=>`${item.title}(${item.name})`))
|
logger.log("Supported languages\t: {}",options.languages.map(item=>`${item.title}(${item.name})`))
|
||||||
logger.log("Default language\t: {}",options.defaultLanguage)
|
logger.log("Default language\t: {}",options.defaultLanguage)
|
||||||
logger.log("Active language\t\t: {}",options.activeLanguage)
|
logger.log("Active language\t\t: {}",options.activeLanguage)
|
||||||
@ -321,17 +320,9 @@ function updateLanguageFile(fromTexts,toLangFile,options){
|
|||||||
if(langName.startsWith("$")) return //
|
if(langName.startsWith("$")) return //
|
||||||
const langExists = langName in targetLangs
|
const langExists = langName in targetLangs
|
||||||
const targetText = targetLangs[langName]
|
const targetText = targetLangs[langName]
|
||||||
// 如果目标语言已经存在并且内容不为空,则不需要更新
|
// 如果目标语言已经存在并且内容不为空,则不需要更新
|
||||||
if(!langExists){
|
if(!langExists){ // 不存在则创建新的翻译条目
|
||||||
targetLangs[langName] = sourceText
|
targetLangs[langName] = sourceText
|
||||||
}else if(typeof(targetText) === "string" && targetText.trim().length==0){
|
|
||||||
targetLangs[langName] = sourceText
|
|
||||||
}else if(Array.isArray(targetText)){// 当文本内容支持复数时,可以用[单数文本,复数文本]的形式
|
|
||||||
if(targetText.length>0){
|
|
||||||
targetLangs[langName] = sourceText
|
|
||||||
}else{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
@ -397,14 +388,14 @@ module.exports = function(options={}){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 将元数据生成到 i18n.meta.json
|
// 将元数据生成到 i18n.meta.json
|
||||||
const metaFile = path.join(outputPath,"i18n.meta.json")
|
const metaFile = path.join(outputPath,"i18n.settings.js")
|
||||||
const meta = {
|
const meta = {
|
||||||
languages : options.languages,
|
languages : options.languages,
|
||||||
defaultLanguage: options.defaultLanguage,
|
defaultLanguage: options.defaultLanguage,
|
||||||
activeLanguage : options.activeLanguage,
|
activeLanguage : options.activeLanguage,
|
||||||
namespaces : options.namespaces
|
namespaces : options.namespaces
|
||||||
}
|
}
|
||||||
fs.writeFileSync(metaFile,JSON.stringify(meta,null,4))
|
fs.writeFileSync(metaFile,`export default ${JSON.stringify(meta,null,4)}`)
|
||||||
logger.log(" - Generate language metadata : {}",metaFile)
|
logger.log(" - Generate language metadata : {}",metaFile)
|
||||||
callback()
|
callback()
|
||||||
});
|
});
|
||||||
|
33
src/formatters.js
Normal file
33
src/formatters.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 默认的格式化器
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
*
|
||||||
|
* 在translates/xxx.json文件中进行翻译时,可以对插值变量进行格式化,
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "Now is {date}":{
|
||||||
|
* "zh-CN":"现在是{date|time}"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
cn:{
|
||||||
|
Date:{
|
||||||
|
default:(value)=>dayjs(value).format("YYYY年MM年DD日"), // 默认的变量格式化器
|
||||||
|
time:(value)=>dayjs(value).format("HH:mm:ss"),
|
||||||
|
short:(value)=>dayjs(value).format("YYYY/MM/DD"),
|
||||||
|
},
|
||||||
|
Number:{
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
en:{
|
||||||
|
"Date":{
|
||||||
|
short:(value)=>dayjs(value).format("YYYY/MM/DD"),
|
||||||
|
time:(value)=>dayjs(value).format("HH:mm:ss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
src/utils.js
Normal file
126
src/utils.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// 用来提取字符里面的插值变量参数
|
||||||
|
// let varRegexp = /\{\s*(?<var>\w*\.?\w*)\s*\}/g
|
||||||
|
let varRegexp = /\{\s*((?<varname>\w+)?(\s*\|\s*(?<formatter>\w*))?)?\s*\}/g
|
||||||
|
// 插值变量字符串替换正则
|
||||||
|
//let varReplaceRegexp =String.raw`\{\s*(?<var>{name}\.?\w*)\s*\}`
|
||||||
|
|
||||||
|
|
||||||
|
let varReplaceRegexp =String.raw`\{\s*{varname}\s*\}`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考虑到通过正则表达式进行插件的替换可能较慢,因此提供一个简单方法来过滤掉那些
|
||||||
|
* 不需要进行插值处理的字符串
|
||||||
|
* 原理很简单,就是判断一下是否同时具有{、}字符
|
||||||
|
* 注意:该方法只能快速判断一个字符串不包括插值变量
|
||||||
|
* @param {*} str
|
||||||
|
* @returns {boolean} true=可能包含插值变量,
|
||||||
|
*/
|
||||||
|
function hasInterpolation(str){
|
||||||
|
return str.includes("{") && str.includes("}")
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取指定变量类型名称
|
||||||
|
* getDataTypeName(1) == Number
|
||||||
|
* getDataTypeName("") == String
|
||||||
|
* getDataTypeName(null) == Null
|
||||||
|
* getDataTypeName(undefined) == Undefined
|
||||||
|
* getDataTypeName(new Date()) == Date
|
||||||
|
* getDataTypeName(new Error()) == Error
|
||||||
|
*
|
||||||
|
* @param {*} v
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function getDataTypeName(v){
|
||||||
|
if (v === null) return 'Null'
|
||||||
|
if (v === undefined) return 'Undefined'
|
||||||
|
if(typeof(v)==="function") return "Function"
|
||||||
|
return v.constructor && v.constructor.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取字符串中的插值变量
|
||||||
|
* @param {*} str
|
||||||
|
* @returns {Array} 变量名称列表
|
||||||
|
*/
|
||||||
|
function getInterpolatedVars(str){
|
||||||
|
let result = []
|
||||||
|
let match
|
||||||
|
while ((match = varRegexp.exec(str)) !== null) {
|
||||||
|
if (match.index === varRegexp.lastIndex) {
|
||||||
|
varRegexp.lastIndex++;
|
||||||
|
}
|
||||||
|
if(match.groups.varname) {
|
||||||
|
result.push(match.groups.formatter ? match.groups.varname+"|"+match.groups.formatter : match.groups.varname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformVarValue(value){
|
||||||
|
let result = value
|
||||||
|
if(typeof(result)==="function") result = value()
|
||||||
|
if(!(typeof(result)==="string")){
|
||||||
|
if(Array.isArray(result) || typeof(result)==="object"){
|
||||||
|
result = JSON.stringify(result)
|
||||||
|
}else{
|
||||||
|
result = result.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 字符串可以进行变量插值替换,
|
||||||
|
- 当只有两个参数并且第2个参数是{}时,将第2个参数视为命名变量的字典
|
||||||
|
replaceVars("this is {a}+{b},{a:1,b:2}) --> this is 1+2
|
||||||
|
- 当只有两个参数并且第2个参数是[]时,将第2个参数视为位置参数
|
||||||
|
replaceVars"this is {}+{}",[1,2]) --> this is 1+2
|
||||||
|
- 普通位置参数替换
|
||||||
|
replaceVars("this is {a}+{b}",1,2) --> this is 1+2
|
||||||
|
-
|
||||||
|
|
||||||
|
* @param {*} template
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function replaceInterpolateVars(template,...args) {
|
||||||
|
let result=template
|
||||||
|
if(!hasInterpolation(template)) return
|
||||||
|
if(args.length===1 && typeof(args[0]) === "object" && !Array.isArray(args[0])){ // 变量插值
|
||||||
|
for(let name in args[0]){
|
||||||
|
// 如果变量中包括|管道符,则需要进行转换以适配更宽松的写法,比如data|time能匹配"data |time","data | time"等
|
||||||
|
let nameRegexp = name.includes("|") ? name.split("|").join("\\s*\\|\\s*") : name
|
||||||
|
result=result.replaceAll(new RegExp(varReplaceRegexp.replaceAll("{varname}",nameRegexp),"g"),transformVarValue(args[0][name]))
|
||||||
|
}
|
||||||
|
}else{ // 位置插值
|
||||||
|
const params=(args.length===1 && Array.isArray(args[0])) ? [...args[0]] : args
|
||||||
|
let i=0
|
||||||
|
for(let match of result.match(varRegexp) || []){
|
||||||
|
if(i<params.length){
|
||||||
|
let param = transformVarValue(params[i])
|
||||||
|
result=result.replace(match,param)
|
||||||
|
i+=1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const str = "I am {name}, I am {age} years old. you are {name},Now is {date},time={date | time}?"
|
||||||
|
|
||||||
|
console.log("vars=",getInterpolatedVars(str).join())
|
||||||
|
|
||||||
|
console.log(replaceInterpolateVars(str,{name:"tom",age:18,date:new Date(),"date|time":new Date().getTime()}))
|
||||||
|
console.log(replaceInterpolateVars(str,"tom",18,"jack"))
|
||||||
|
console.log(replaceInterpolateVars(str,["tom",18,"jack",1,2]))
|
||||||
|
console.log(replaceInterpolateVars(str,"tom",18,()=>"bob"))
|
||||||
|
console.log(replaceInterpolateVars(str,"tom",[1,2],{a:1},1,2))
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hasInterpolation,
|
||||||
|
getInterpolatedVars,
|
||||||
|
replaceInterpolateVars
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user