update
This commit is contained in:
parent
5fb36e4a45
commit
b9eff3ea5b
@ -2,7 +2,7 @@
|
||||
"name": "voerka-i18n",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"extract": ""
|
||||
|
54
readme.md
54
readme.md
@ -113,3 +113,57 @@ t("第{}章",7) // == Chapter Seven
|
||||
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 fs = require('fs')
|
||||
const readJson = require("readjson")
|
||||
const createLogger = require("logsets")
|
||||
|
||||
const createLogger = require("logsets")
|
||||
const { replaceInterpolateVars,getDataTypeName } = require("./utils")
|
||||
const logger = createLogger()
|
||||
|
||||
// 捕获翻译文本的默认正则表达式
|
||||
@ -190,7 +190,7 @@ function normalizeLanguageOptions(options){
|
||||
// 以下变量会被用来传递给提取器正则表达式
|
||||
translation : {
|
||||
funcName : "t", // 翻译函数名称
|
||||
attrName :"data-i18n", // 用在html组件上的翻译属性名称
|
||||
attrName :"data-i18n", // 用在html组件上的翻译属性名称
|
||||
}
|
||||
},options)
|
||||
// 输出配置
|
||||
@ -270,8 +270,7 @@ function normalizeLanguageOptions(options){
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
logger.log("Supported languages\t: {}",options.languages.map(item=>`${item.title}(${item.name})`))
|
||||
logger.log("Default language\t: {}",options.defaultLanguage)
|
||||
logger.log("Active language\t\t: {}",options.activeLanguage)
|
||||
@ -321,17 +320,9 @@ function updateLanguageFile(fromTexts,toLangFile,options){
|
||||
if(langName.startsWith("$")) return //
|
||||
const langExists = langName in targetLangs
|
||||
const targetText = targetLangs[langName]
|
||||
// 如果目标语言已经存在并且内容不为空,则不需要更新
|
||||
if(!langExists){
|
||||
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{
|
||||
|
||||
}
|
||||
// 如果目标语言已经存在并且内容不为空,则不需要更新
|
||||
if(!langExists){ // 不存在则创建新的翻译条目
|
||||
targetLangs[langName] = sourceText
|
||||
}
|
||||
})
|
||||
}else{
|
||||
@ -397,14 +388,14 @@ module.exports = function(options={}){
|
||||
}
|
||||
}
|
||||
// 将元数据生成到 i18n.meta.json
|
||||
const metaFile = path.join(outputPath,"i18n.meta.json")
|
||||
const metaFile = path.join(outputPath,"i18n.settings.js")
|
||||
const meta = {
|
||||
languages : options.languages,
|
||||
defaultLanguage: options.defaultLanguage,
|
||||
activeLanguage : options.activeLanguage,
|
||||
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)
|
||||
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