add typescript supports

This commit is contained in:
wxzhang 2023-01-09 18:09:10 +08:00
parent 350012a113
commit cdc732ada7
15 changed files with 467 additions and 286 deletions

View File

@ -1,32 +0,0 @@
module.exports = {
"1": "a",
"2": "b",
"3": "c",
"4": "d",
"5": "e",
"6": "请输入旧密码:",
"7": "请再次输入旧密码:",
"8": "请输入新密码:",
"9": "请再次输入新密码:",
"10": "密码至少需要6位并且至少包含数字、字符或特殊符号中的两种",
"11": "密码强度: {strength}",
"12": "用户名或密码错误",
"13": "请输入用户名:",
"14": "请输入密码:",
"15": "欢迎您: {}",
"16": "数据库类型:{}、{}、{}",
"17": "数据库密码:{pwd}",
"18": "数据库地址:{url}",
"19": "编码:{encode}",
"20": "编码",
"21": "名称",
"22": "描述",
"23": "文件名称",
"24": "您有{}条未读消息",
"25": "消息总数:{$count}",
"26": "消息类型:{type}",
"27": "登录",
"28": "请输入密码:",
"29": "头像",
"30": "相片"
}

View File

@ -1,32 +0,0 @@
export default {
"1": "a",
"2": "b",
"3": "c",
"4": "d",
"5": "e",
"6": "Please enter your old password:",
"7": "Please enter your old password again:",
"8": "Please enter a new password:",
"9": "Please enter the new password again:",
"10": "The password needs at least 6 digits and contains at least two of numbers, characters or special symbols",
"11": "Password strength: {strength}",
"12": "Wrong user name or password",
"13": "Please enter user name:",
"14": "Please input a password:",
"15": "Welcome: {}",
"16": "Database type: {}, {}, {}",
"17": "Database password: {PWD}",
"18": "Database address: {URL}",
"19": "Code: {encode}",
"20": "code",
"21": "name",
"22": "describe",
"23": "File name",
"24": "You have {} unread messages",
"25": "Total messages: {$count}",
"26": "Message type: {type}",
"27": "Sign in",
"28": "Please input a password:",
"29": "head portrait",
"30": "photo"
}

View File

@ -1,106 +0,0 @@
/**
格式化器用来对翻译文本内容中的插值变量进行格式化
比如将一个数字格式化为货币格式或者将一个日期格式化为友好的日期格式
- 以下定义了一些格式化器在中文场景下会启用这些格式化器
import dayjs from "dayjs";
const formatters = {
"*":{ // 在所有语言下生效的格式化器
$types:{...}, // 只作用于特定数据类型的默认格式化器
.... // 全局格式化器
},
cn:{
// 只作用于特定数据类型的格式化器
$types:{
Date:(value)=>dayjs(value).format("YYYY年MM月DD日 HH:mm:ss"),
},
date:(value)=>dayjs(value).format("YYYY年MM月DD日")
bjTime:(value)=>"北京时间"+ value,
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
},
en:{
$types:{
Date:(value)=>dayjs(value).format("YYYY/MM/DD HH:mm:ss"), // 默认的格式化器
},
date:(value)=>dayjs(value).format("YYYY/MM/DD")
bjTime:(value)=>"BeiJing "+ value,
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
[格式化器名称]:(value)=>{...},
}
}
- 在翻译函数中使用格式化器的方法,示例如下
t("Now is { value | date | bjTime }",{value: new Date()})
其等效于
t(`Now is ${bjTime(date(value))",{value: new Date()})
由于value分别经过两个管道符转换上一个管道符的输出作为下一个管道符的输入,可以多次使用管道符
最终的输出结果
中文: "现在是北京时间2022年3月1日"
英文: "Now is BeiJing 2022/03/01"
*
*/
export default {
// 在所有语言下生效的格式化器
"*":{
//[格式化名称]:(value)=>{...},
//[格式化名称]:(value,arg)=>{...},
},
// 在所有语言下只作用于特定数据类型的格式化器
$types:{
},
cn:{
$types:{
// 所有类型的默认格式化器
// "*":{
// },
// Date:{
// },
// Number:{
// },
// String:{
// },
// Array:{
// },
// Object:{
// }
}
},
en:{
$types:{
// 所有类型的默认格式化器
// "*":{
// },
// Date:{
// },
// Number:{
// },
// String:{
// },
// Array:{
// },
// Object:{
// }
}
}
}

View File

@ -1,32 +0,0 @@
export default {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"请输入旧密码:": 6,
"请再次输入旧密码:": 7,
"请输入新密码:": 8,
"请再次输入新密码:": 9,
"密码至少需要6位并且至少包含数字、字符或特殊符号中的两种": 10,
"密码强度: {strength}": 11,
"用户名或密码错误": 12,
"请输入用户名:": 13,
"请输入密码:": 14,
"欢迎您: {}": 15,
"数据库类型:{}、{}、{}": 16,
"数据库密码:{pwd}": 17,
"数据库地址:{url}": 18,
"编码:{encode}": 19,
"编码": 20,
"名称": 21,
"描述": 22,
"文件名称": 23,
"您有{}条未读消息": 24,
"消息总数:{$count}": 25,
"消息类型:{type}": 26,
"登录": 27,
"请输入密码:": 28,
"头像": 29,
"相片": 30
}

View File

@ -1,57 +0,0 @@
import messageIds from "./idMap.js"
import runtime from "./runtime.js"
const { translate,i18nScope } = runtime
import formatters from "./formatters.js"
import defaultMessages from "./zh.js"
const activeMessages = defaultMessages
// 语言配置文件
const scopeSettings = {
"languages": [
{
"name": "zh",
"title": "中文"
},
{
"name": "en",
"title": "英语"
},
{
"name": "de",
"title": "德语"
},
{
"name": "jp",
"title": "日语"
}
],
"defaultLanguage": "zh",
"activeLanguage": "zh",
"namespaces": {}
}
// 语言作用域
const scope = new i18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id: "", // 当前作用域的id自动取当前工程的package.json的name
default: defaultMessages, // 默认语言包
messages : activeMessages, // 当前语言包
idMap:messageIds, // 消息id映射列表
formatters, // 当前作用域的格式化函数列表
loaders:{
"en" : ()=>import("./en.js"),
"de" : ()=>import("./de.js"),
"jp" : ()=>import("./jp.js")
}
})
// 翻译函数
const scopedTtranslate = translate.bind(scope)
export {
scopedTtranslate as t,
scope as i18nScope
}

View File

@ -0,0 +1,108 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
//"suppressImplicitAnyIndexErrors":true,
/* Language and Environment */
"target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["ES2015"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
//"moduleResolution": "node16",
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "baseUrl": ".",
// "rootDir": "./src",
// "outDir": "./dist",
// "paths": {
// },
//"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
//"baseUrl": "./dist", /* Specify the base directory to resolve non-relative module names. */
//"paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
//"rootDirs": ["src"], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@ -33,7 +33,8 @@ const artTemplate = require("art-template")
function normalizeCompileOptions(opts={}) { function normalizeCompileOptions(opts={}) {
let options = Object.assign({ let options = Object.assign({
moduleType:"auto" // 指定编译后的语言文件的模块类型取值common,cjs,esm,es moduleType:"auto", // 指定编译后的语言文件的模块类型取值common,cjs,esm,es
isTypeScript:false
}, opts) }, opts)
options.moduleType = options.moduleType.trim() options.moduleType = options.moduleType.trim()
if(options.moduleType==="es") options.moduleType = "esm" if(options.moduleType==="es") options.moduleType = "esm"
@ -42,15 +43,15 @@ function normalizeCompileOptions(opts={}) {
return options; return options;
} }
function generateFormatterFile(langName,{formattersFolder,templateContext,moduleType}={}){ function generateFormatterFile(langName,{isTypeScript,formattersFolder,templateContext,moduleType}={}){
const formattersFile = path.join(formattersFolder,`${langName}.js`) const formattersFile = path.join(formattersFolder,`${langName}.${isTypeScript ? 'ts' : 'js'}`)
if(!fs.existsSync(formattersFile)){ if(!fs.existsSync(formattersFile)){
const formattersContent = artTemplate(path.join(__dirname,"templates","formatters.js"), templateContext ) const formattersContent = artTemplate(path.join(__dirname,"templates",`formatters.${isTypeScript ? 'ts' : 'js'}`), templateContext )
fs.writeFileSync(formattersFile,formattersContent) fs.writeFileSync(formattersFile,formattersContent)
logger.log(t(" - 格式化器:{}"),path.basename(formattersFile)) logger.log(t(" - 格式化器:{}"),path.basename(formattersFile))
}else{ // 格式化器如果存在,则需要更改对应的模块类型 }else{ // 格式化器如果存在,则需要更改对应的模块类型
let formattersContent = fs.readFileSync(formattersFile,"utf8").toString() let formattersContent = fs.readFileSync(formattersFile,"utf8").toString()
if(moduleType == "esm"){ if(moduleType == "esm" || isTypeScript){
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ") formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\s*\=/gm,"export default ")
formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ") formattersContent = formattersContent.replaceAll(/^[^\n\r\w]*module.exports\./gm,"export ")
}else{ }else{
@ -65,7 +66,7 @@ function generateFormatterFile(langName,{formattersFolder,templateContext,module
module.exports =async function compile(langFolder,opts={}){ module.exports =async function compile(langFolder,opts={}){
const options = normalizeCompileOptions(opts); const options = normalizeCompileOptions(opts);
let { moduleType,inlineRuntime } = options; let { moduleType,inlineRuntime,isTypeScript } = options;
// 如果自动则会从当前项目读取如果没有指定则会是esm // 如果自动则会从当前项目读取如果没有指定则会是esm
if(moduleType==="auto"){ if(moduleType==="auto"){
moduleType = findModuleType(langFolder) moduleType = findModuleType(langFolder)
@ -85,6 +86,7 @@ module.exports =async function compile(langFolder,opts={}){
logger.log(t("激活语言\t: {}"),activeLanguage) logger.log(t("激活语言\t: {}"),activeLanguage)
logger.log(t("名称空间\t: {}"),Object.keys(namespaces).join(",")) logger.log(t("名称空间\t: {}"),Object.keys(namespaces).join(","))
logger.log(t("模块类型\t: {}"),moduleType) logger.log(t("模块类型\t: {}"),moduleType)
logger.log(t("TypeScript\t: {}"),isTypeScript)
logger.log("") logger.log("")
logger.log(t("编译结果输出至:{}"),langFolder) logger.log(t("编译结果输出至:{}"),langFolder)
@ -118,9 +120,9 @@ module.exports =async function compile(langFolder,opts={}){
Object.entries(messages).forEach(([message,translatedMsgs])=>{ Object.entries(messages).forEach(([message,translatedMsgs])=>{
langMessages[translatedMsgs.$id] = lang.name in translatedMsgs ? translatedMsgs[lang.name] : message langMessages[translatedMsgs.$id] = lang.name in translatedMsgs ? translatedMsgs[lang.name] : message
}) })
const langFile = path.join(langFolder,`${lang.name}.js`) const langFile = path.join(langFolder,`${lang.name}.${isTypeScript ? 'ts' : 'js'}`)
// 为每一种语言生成一个语言文件 // 为每一种语言生成一个语言文件
if(moduleType==="esm"){ if(moduleType==="esm" || isTypeScript){
fs.writeFileSync(langFile,`export default ${JSON.stringify(langMessages,null,4)}`) fs.writeFileSync(langFile,`export default ${JSON.stringify(langMessages,null,4)}`)
}else{ }else{
fs.writeFileSync(langFile,`module.exports = ${JSON.stringify(langMessages,null,4)}`) fs.writeFileSync(langFile,`module.exports = ${JSON.stringify(langMessages,null,4)}`)
@ -129,16 +131,15 @@ module.exports =async function compile(langFolder,opts={}){
}) })
// 4. 生成id映射文件 // 4. 生成id映射文件
const idMapFile = path.join(langFolder,"idMap.js") const idMapFile = path.join(langFolder,`idMap.${isTypeScript ? 'ts' : 'js'}`)
if(moduleType==="esm"){ if(moduleType==="esm" || isTypeScript){
fs.writeFileSync(idMapFile,`export default ${JSON.stringify(messageIds,null,4)}`) fs.writeFileSync(idMapFile,`export default ${JSON.stringify(messageIds,null,4)}`)
}else{ }else{
fs.writeFileSync(idMapFile,`module.exports = ${JSON.stringify(messageIds,null,4)}`) fs.writeFileSync(idMapFile,`module.exports = ${JSON.stringify(messageIds,null,4)}`)
} }
logger.log(t(" - idMap文件: {}"),path.basename(idMapFile)) logger.log(t(" - idMap文件: {}"),path.basename(idMapFile))
// 嵌入运行时源码 // 嵌入运行时源码
if(inlineRuntime){ if(inlineRuntime && !isTypeScript ){
const runtimeSourceFolder = path.join(require.resolve("@voerkai18n/runtime"),"../..") const runtimeSourceFolder = path.join(require.resolve("@voerkai18n/runtime"),"../..")
fs.copyFileSync( fs.copyFileSync(
path.join(runtimeSourceFolder,"dist",`runtime.${moduleType === 'esm' ? 'mjs' : 'cjs'}`), path.join(runtimeSourceFolder,"dist",`runtime.${moduleType === 'esm' ? 'mjs' : 'cjs'}`),
@ -154,6 +155,7 @@ module.exports =async function compile(langFolder,opts={}){
logger.log(t(" - 更新运行时:{}"),"@voerkai18n/runtime") logger.log(t(" - 更新运行时:{}"),"@voerkai18n/runtime")
} }
} }
const templateContext = { const templateContext = {
scopeId:projectPackageJson.name, scopeId:projectPackageJson.name,
inlineRuntime, inlineRuntime,
@ -162,6 +164,7 @@ module.exports =async function compile(langFolder,opts={}){
activeLanguage, activeLanguage,
namespaces, namespaces,
moduleType, moduleType,
isTypeScript,
JSON, JSON,
settings:JSON.stringify(langSettings,null,4) settings:JSON.stringify(langSettings,null,4)
} }
@ -170,16 +173,16 @@ module.exports =async function compile(langFolder,opts={}){
if(!fs.existsSync(formattersFolder)) fs.mkdirSync(formattersFolder) if(!fs.existsSync(formattersFolder)) fs.mkdirSync(formattersFolder)
// 为每一个语言生成一个对应的式化器 // 为每一个语言生成一个对应的式化器
languages.forEach(lang=>{ languages.forEach(lang=>{
generateFormatterFile(lang.name,{formattersFolder,templateContext,moduleType}) generateFormatterFile(lang.name,{isTypeScript,formattersFolder,templateContext,moduleType})
}) })
// 6. 生成编译后的访问入口文件 // 6. 生成编译后的访问入口文件
const entryFile = path.join(langFolder,"index.js") const entryFile = path.join(langFolder,`index.${isTypeScript ? 'ts' : 'js'}`)
const entryContent = artTemplate(path.join(__dirname,"templates","entry.js"), templateContext ) const entryContent = artTemplate(path.join(__dirname,"templates",`entry.${isTypeScript ? 'ts' : 'js'}`), templateContext )
fs.writeFileSync(entryFile,entryContent) fs.writeFileSync(entryFile,entryContent)
logger.log(t(" - 访问入口文件: {}"),path.basename(entryFile)) logger.log(t(" - 访问入口文件: {}"),path.basename(entryFile))
}catch(e){ }catch(e){
logger.log(t("加载多语言配置文件<{}>失败: {} "),settingsFile,e.message) logger.log(t("加载多语言配置文件<{}>失败: {} "),settingsFile,e.stack)
} }
} }

View File

@ -6,7 +6,7 @@ const path = require("path")
const fs = require("fs-extra") const fs = require("fs-extra")
const logger = createLogger() const logger = createLogger()
const { i18nScope ,t } = require("./i18nProxy") const { i18nScope ,t } = require("./i18nProxy")
const { getProjectSourceFolder } = require("@voerkai18n/utils"); const { getProjectSourceFolder,isTypeScriptProject } = require("@voerkai18n/utils");
logger.use(bannerPluin) logger.use(bannerPluin)
@ -100,6 +100,7 @@ program
}) })
.action(async (location,options) => { .action(async (location,options) => {
location = getProjectSourceFolder(location) location = getProjectSourceFolder(location)
options.isTypeScript = isTypeScriptProject()
const langFolder = path.join(location,"languages") const langFolder = path.join(location,"languages")
if(!fs.existsSync(langFolder)){ if(!fs.existsSync(langFolder)){
logger.error(t("语言包文件夹<{}>不存在",langFolder)) logger.error(t("语言包文件夹<{}>不存在",langFolder))

View File

@ -0,0 +1,41 @@
import messageIds from "./idMap" // 语言ID映射文件
{{if inlineRuntime && !isTypeScript }}import runtime from "./runtime" // 运行时
const { translate,i18nScope } = runtime
import defaultFormatters from "./formatters/{{defaultLanguage}}" // 默认语言格式化器
{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "./formatters/{{activeLanguage}}"{{/if}} //
{{else}}import { translate,i18nScope } from "@voerkai18n/runtime"
import defaultFormatters from "./formatters/{{defaultLanguage}}" // 默认语言格式化器
{{if defaultLanguage === activeLanguage}}const activeFormatters = defaultFormatters{{else}}import activeFormatters from "@voerkai18n/runtime/formatters/{{activeLanguage}}"{{/if}}
{{/if}}
import defaultMessages from "./{{defaultLanguage}}"
{{if defaultLanguage === activeLanguage}}const activeMessages = defaultMessages{{else}}import activeMessages from "./{{activeLanguage}}"{{/if}}
// 语言配置文件
const scopeSettings = {{@ settings}}
const formatters = {
{{each languages}}{{if $value.name == defaultLanguage}}'{{defaultLanguage}}' : defaultFormatters{{if $index !== languages.length - 1}},{{/if}}
{{else if $value.name == activeLanguage}}{{if defaultLanguage !== activeLanguage}}'{{activeLanguage}}':activeFormatters{{/if}}{{if $index !== languages.length - 1}},{{/if}}
{{else}}'{{$value.name}}' : ()=>import("./formatters/{{$value.name}}"){{if $index !== languages.length - 1}},{{'\n\t'}}{{/if}}{{/if}}{{/each}}
}
// 语言包加载器
const loaders = { {{each languages}}{{if $value.name !== defaultLanguage}}
{{if $value.name == activeLanguage}}"{{$value.name}}" : activeMessages{{else}}"{{$value.name}}" : ()=>import("./{{$value.name}}"){{/if}}{{if $index !== languages.length - 1}},{{/if}}{{/if}}{{/each}}
}
// 语言作用域
const scope = new i18nScope({
...scopeSettings, // languages,defaultLanguage,activeLanguage,namespaces,formatters
id : "{{scopeId}}", // 当前作用域的id自动取当前工程的package.json的name
debug : false, // 是否在控制台输出高度信息
default : defaultMessages, // 默认语言包
messages : activeMessages, // 当前语言包
idMap : messageIds, // 消息id映射列表
formatters, // 扩展自定义格式化器
loaders // 语言包加载器
})
// 翻译函数
const scopedTtranslate = translate.bind(scope)
export {
scopedTtranslate as t,
scope as i18nScope
}

View File

@ -0,0 +1,112 @@
/**
*/
// import { Formatter,FlexFormatter } from "./runtime"
export default {
// global : true, // 简单地设置为true,代表当前所有格式化器均注册到全局false只在当前scope生效
// global : { // 仅将里面的格式化器注册到全局
// $config:{... }
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
//}, // 是否注册到全局false只在当前scope生效
// 直接对内置格式化器进行配置,请参阅官网文档
// $config:{
// datetime : {
// units : ["Year","Quarter","Month","Week","Day","Hour","Minute","Second","Millisecond","Microsecond"],
// date :{
// long : 'YYYY/MM/DD HH:mm:ss',
// short : "YYYY/MM/DD",
// format : "local"
// },
// quarter : {
// long : ["First Quarter","Second Quarter","Third Quarter","Fourth Quarter"],
// short : ["Q1","Q2","Q3","Q4"],
// format : "short"
// },
// month:{
// long : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
// short : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"],
// format : "long" // 0-长名称1-短名称2-数字
// },
// weekday:{
// long : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
// short : ["Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat"],
// format : "long", // 0-长名称1-短名称2-数字
// },
// time : {
// long : "HH:mm:ss",
// short : "HH:mm:ss",
// format : 'local'
// },
// timeSlots : {
// slots : [12],
// lowerCases : ["am","pm"],
// upperCases : ["AM","PM"]
// },
// relativeTime : {
// units : ["seconds","minutes","hours","days","weeks","months","years"],
// now : "Now",
// before : "{value} {unit} ago",
// after : "after {value} {unit}"
// }
// },
// currency : {
// default : "{symbol}{value}{unit}",
// long : "{prefix} {symbol}{value}{unit}{suffix}",
// short : "{symbol}{value}{unit}",
// custom : "{prefix} {symbol}{value}{unit}{suffix}",
// format : "default",
// //--
// units : [""," thousands"," millions"," billions"," trillions"], //千,百万,十亿,万亿
// radix : 3, // 进制即三位一进中文是4位一进
// symbol : "$", // 符号
// prefix : "USD", // 前缀
// suffix : "", // 后缀
// division : 3, // ,分割位
// precision : 2, // 精度
// },
// number : {
// division : 3, // , 分割位3代表每3位添加一个,
// precision : 0 // 精度,即保留小数点位置,0代表不限
// },
// empty:{
// //values : [], // 可选定义空值如果想让0,''也为空值可以指定values=[0,'']
// escape : "", // 当空值时显示的备用值
// next : 'break' // 当空值时下一步的行为: break=中止;skip=跳过
// },
// error : {
// //当错误时显示的内容支持的插值变量有message=错误信息,error=错误类名,也可以是一个返回上面内容的同步函数
// escape : null, // 默认当错误时显示空内容
// next : 'break' // 当出错时下一步的行为: break=中止;skip=忽略
// },
// fileSize:{
// brief : ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB","NB","DB"],
// whole : ["Bytes", "Kilobytes", "Megabytes", "Gigabytes", "TeraBytes", "PetaBytes", "ExaBytes", "ZetaBytes", "YottaBytes","DoggaBytes"],
// precision: 2 // 小数精度
// }
// },
// 改变特定数据类型的默认格式化器
// $types:{
// Date : dateFormatter,
// Null : value =>"",
// Undefined: value =>"",
// Error : value => "ERROR",
// Boolean : value =>value ? "True":"False",
// Number : numberFormartter
// }
// 以下可以自定义编写格式化器
// xxxx : value => { ... },
// xxxx : (value,$config) => { ... },
// xxxx : (value,...args,$config) => { ... },
// xxxx : Formatter(value,...args,$config) => { ... },
// xxxx : FlexFormatter(value,params,$config) => { ... },
}

130
packages/runtime/index.d.ts vendored Normal file
View File

@ -0,0 +1,130 @@
export type VoerkaLanguageMessages = Record<string,string>
export interface VoerkaLanguagePack {
[key: string]: VoerkaLanguageMessages
}
export interface VoerkaI18nManagerSettings {
debug?:boolean
defaultLanguage: string
activeLanguage : string
formatters : VoerkI18nFormatters
languages :VoerkaI18nLanguage[]
}
export class I18nManager{
constructor(settings:VoerkaI18nManagerSettings)
get settings():Required<VoerkaI18nManagerSettings> // 配置参数
get scopes():i18nScope[] // 注册的报有i18nScope实例q
get activeLanguage():string // 当前激活语言名称
get defaultLanguage():string // 默认语言名称
get languages():VoerkaI18nSupportedLanguages // 支持的语言列表
get formatters():VoerkI18nFormatters // 内置格式化器{*:{$config,$types,...},zh:{$config,$types,...},en:{$config,$types,...}}
get defaultMessageLoader():VoerkI18nLoader // 默认语言包加载器
// 通过默认加载器加载文件
loadMessagesFromDefaultLoader(newLanguage:string,scope:i18nScope):Promise<VoerkaLanguageMessages>
change(language:string):Promise<void>
register(scope:i18nScope):Promise<void>
registerFormatter(name:string,formatter:VoerkI18nFormatter,options?:{language:string | string[]}):void
registerDefaultLoader(fn:VoerkI18nLoader):void
refresh():Promise<void>
}
export type Voerkai18nIdMap = Record<string,number>
export interface VoerkaI18nLanguage{
name:string
title?:string
default?:boolean
fallback?:string
}
export interface VoerkaI18nSupportedLanguages {
[key :string]:VoerkaI18nLanguage
}
export type VoerkI18nFormatter = (value:string,...args:any[]) => string
export type VoerkI18nFormatterConfigs = Record<string,any>
export type VoerkI18nFormatters = Record<string,({
$types?:Record<string,VoerkI18nFormatter>
$config?:Record<string,string>
} & {
[key:string]:VoerkI18nFormatter
}) | (() => Awaited<Promise<any>>)>
export type VoerkI18nLoader = ()=>Awaited<Promise<any>>
export interface VoerkI18nLoaders {
[key :string]:VoerkI18nLoader
}
export interface I18nScopeOptions{
id?:string
debug?:boolean
languages:VoerkaI18nLanguage[]
defaultLanguage:string // 默认语言名称
activeLanguage:string // 当前语言名称
default:VoerkaLanguageMessages // 默认语言包
messages:VoerkaLanguageMessages // 当前语言包
idMap:Voerkai18nIdMap // 消息id映射列表
formatters:VoerkI18nFormatters // 当前作用域的格式化函数列表{<lang>: {$types,$config,[格式化器名称]: () => {},[格式化器名称]: () => {}}}
loaders:VoerkI18nLoaders; // 异步加载语言文件的函数列表
}
export class i18nScope {
constructor(options:I18nScopeOptions, callback?:Function)
get id():string // 作用域唯一id
get debug():boolean // 调试开关
get defaultLanguage():string // 默认语言名称
get activeLanguage():string // 默认语言名称
get default():VoerkaLanguageMessages // 默认语言包
get messages(): VoerkaLanguageMessages // 当前语言包
get idMap():Voerkai18nIdMap // 消息id映射列表
get languages():VoerkaI18nSupportedLanguages // 当前作用域支持的语言列表[{name,title,fallback}]
get loaders() :VoerkI18nLoaders // 异步加载语言文件的函数列表
get global():I18nManager // 引用全局VoerkaI18n配置注册后自动引用
get formatters():VoerkI18nFormatters // 当前作用域的所有格式化器定义 {<语言名称>: {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () => {}}}
get activeFormatters():VoerkI18nFormatters // 当前作用域激活的格式化器定义 {$types,$config,[格式化器名称]: () = >{},[格式化器名称]: () = >{}}
get activeFormatterConfig():VoerkI18nFormatterConfigs // 当前格式化器合并后的配置参数,参数已经合并了全局格式化器中的参数
/**
*
* @param {*} callback
*/
register(callback:Function):void
/**
*
*/
registerFormatter(name:string, formatter:VoerkI18nFormatter, { language , global }:{language:string | string[] , global:boolean}):void
/**
*
*/
registerFormatters(formatters:VoerkI18nFormatters,asGlobal?:boolean):void
/**
*
*/
registerDefaultLoader(fn:Function):void
/**
*
* @param {*} language
* @returns
*/
getLanguage(language:string):VoerkaLanguageMessages
hasLanguage(language:string):boolean
refresh(newLanguage:string):Promise<void>
on():void
off():void
offAll():void
change(language:string):Promise<void>
}
export declare type translate =((message:string,...args:(string | Function)[])=>string)
| ((message:string,vars:Record<string,any>)=>string)
declare global {
var VoerkaI18n:I18nManager
var t:((message:string,...args:(string | Function)[])=>string)
| ((message:string,vars:Record<string,any>)=>string)
}

View File

@ -3,7 +3,8 @@
"version": "1.1.6", "version": "1.1.6",
"description": "核心运行时", "description": "核心运行时",
"main": "./dist/index.cjs", "main": "./dist/index.cjs",
"module": "dist/index.esm.js", "module": "./dist/index.esm.js",
"types":"./index.d.ts",
"homepage": "https://gitee.com/zhangfisher/voerka-i18n", "homepage": "https://gitee.com/zhangfisher/voerka-i18n",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -128,6 +128,14 @@ function getProjectRootFolder(folder="./",exclueCurrent=false){
} }
} }
function fileIsExists(filename){
try{
fs.statSync(filename)
return true
}catch(e){
return false
}
}
/** /**
* 自动获取当前项目的languages * 自动获取当前项目的languages
@ -202,6 +210,19 @@ function getProjectRootFolder(folder="./",exclueCurrent=false){
} }
} }
/**
* 判断当前是否是Typescript工程
*
*
*/
function isTypeScriptProject(){
let projectFolder = getProjectRootFolder(process.cwd(),false)
if(projectFolder){
return fileIsExists(path.join(projectFolder,"tsconfig.json"))
|| fileIsExists(path.join(projectFolder,"Src","tsconfig.json"))
}
}
/** /**
* *
* 返回当前项目的模块类型 * 返回当前项目的模块类型
@ -228,7 +249,7 @@ function getProjectRootFolder(folder="./",exclueCurrent=false){
function isInstallDependent(url){ function isInstallDependent(url){
try{ try{
// 简单判断是否存在该文件夹node_modules/@voerkai18n/runtime // 简单判断是否存在该文件夹node_modules/@voerkai18n/runtime
let projectFolder = getCurrentProjectRootFolder(process.cwd()) let projectFolder = getProjectRootFolder(process.cwd())
if(fs.existsSync(path.join(projectFolder,"node_modules","@voerkai18n/runtime"))){ if(fs.existsSync(path.join(projectFolder,"node_modules","@voerkai18n/runtime"))){
return true return true
} }
@ -377,7 +398,7 @@ function deepMerge(toObj,formObj,options={}){
* @param {*} opts * @param {*} opts
*/ */
function installVoerkai18nRuntime(srcPath){ function installVoerkai18nRuntime(srcPath){
const projectFolder = getCurrentProjectRootFolder(srcPath || process.cwd()) const projectFolder = getProjectRootFolder(srcPath || process.cwd())
if(fs.existsSync("pnpm-lock.yaml")){ if(fs.existsSync("pnpm-lock.yaml")){
shelljs.exec("pnpm add @voerkai18n/runtime") shelljs.exec("pnpm add @voerkai18n/runtime")
}else if(fs.existsSync("yarn.lock")){ }else if(fs.existsSync("yarn.lock")){
@ -392,9 +413,9 @@ function deepMerge(toObj,formObj,options={}){
* @param {*} opts * @param {*} opts
*/ */
function updateVoerkai18nRuntime(srcPath){ function updateVoerkai18nRuntime(srcPath){
const projectFolder = getCurrentProjectRootFolder(srcPath || process.cwd()) const projectFolder = getProjectRootFolder(srcPath || process.cwd())
if(fs.existsSync("pnpm-lock.yaml")){ if(fs.existsSync("pnpm-lock.yaml")){
shelljs.exec("pnpm update --latest @voerkai18n/runtime") shelljs.exec("pnpm upgrade --latest @voerkai18n/runtime")
}else if(fs.existsSync("yarn.lock")){ }else if(fs.existsSync("yarn.lock")){
shelljs.exec("yarn upgrade @voerkai18n/runtime") shelljs.exec("yarn upgrade @voerkai18n/runtime")
}else{ }else{
@ -442,4 +463,6 @@ module.exports = {
deepMerge, // 深度合并对象 deepMerge, // 深度合并对象
getDataTypeName, // 获取指定变量类型名称 getDataTypeName, // 获取指定变量类型名称
isGitRepo, // 判断当前工程是否是git工程 isGitRepo, // 判断当前工程是否是git工程
fileIsExists,
isTypeScriptProject
} }

11
versions.test.md Normal file
View File

@ -0,0 +1,11 @@
# 版本信息
| 包| 版本 | 最新发布 | 说明|
| --- | :---: | :---: | --- |
|**@voerkai18n/utils**|1.0.13|2022/08/20|公共工具库|
|**@voerkai18n/runtime**|1.1.6|2022/08/26|核心运行时|
|**@voerkai18n/formatters**|1.0.7|2022/04/15|格式化器,提供对要翻译文本的转换功能|
|**@voerkai18n/react**|1.0.4|2022/04/16|React支持,提供语言切换等功能|
|**@voerkai18n/cli**|1.0.39|2022/08/26|命令行工具,用来初始化/提取/编译/自动翻译等工具链|
|**@voerkai18n/babel**|1.0.24|2022/08/20|Babel插件实现自动导入t函数和自动文本映射|
|**@voerkai18n/vite**|1.0.13|2022/08/20|Vite插件,提供自动插入翻译函数和文本映射等功能|
|**@voerkai18n/vue**|1.0.6|2022/08/20|Vue3插件,提供自动插件翻译函数和语言切换功能|

10
yalc.lock Normal file
View File

@ -0,0 +1,10 @@
{
"version": "v1",
"packages": {
"autopub": {
"signature": "473bffdd0f6747c0e15c60b82fac98cf",
"link": true,
"replaced": "file:.yalc/autopub"
}
}
}