]> git.ipfire.org Git - thirdparty/vuejs/create-vue.git/commitdiff
feat: i18n solution for prompts messages (#348)
authorPan Yue <luventa@outlook.com>
Mon, 16 Oct 2023 08:34:49 +0000 (16:34 +0800)
committerGitHub <noreply@github.com>
Mon, 16 Oct 2023 08:34:49 +0000 (08:34 +0000)
index.ts
locales/en-US.json [new file with mode: 0644]
locales/zh-CN.json [new file with mode: 0644]
package.json
scripts/build.mjs
utils/getLanguage.ts [new file with mode: 0644]

index b5fd686793831e02c21823002fdbcf0d9b5750f7..9daf1f8b564fe0304878e9cfee7d0001efdafe22 100755 (executable)
--- a/index.ts
+++ b/index.ts
@@ -15,6 +15,7 @@ import renderTemplate from './utils/renderTemplate'
 import { postOrderDirectoryTraverse, preOrderDirectoryTraverse } from './utils/directoryTraverse'
 import generateReadme from './utils/generateReadme'
 import getCommand from './utils/getCommand'
+import getLanguage from './utils/getLanguage'
 import renderEslint from './utils/renderEslint'
 import { FILES_TO_FILTER } from './utils/filterList'
 
@@ -115,6 +116,8 @@ async function init() {
 
   const forceOverwrite = argv.force
 
+  const language = getLanguage()
+
   let result: {
     projectName?: string
     shouldOverwrite?: boolean
@@ -148,25 +151,30 @@ async function init() {
         {
           name: 'projectName',
           type: targetDir ? null : 'text',
-          message: 'Project name:',
+          message: language.projectName.message,
           initial: defaultProjectName,
           onState: (state) => (targetDir = String(state.value).trim() || defaultProjectName)
         },
         {
           name: 'shouldOverwrite',
-          type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'confirm'),
+          type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'toggle'),
           message: () => {
             const dirForPrompt =
-              targetDir === '.' ? 'Current directory' : `Target directory "${targetDir}"`
+              targetDir === '.'
+                ? language.shouldOverwrite.dirForPrompts.current
+                : `${language.shouldOverwrite.dirForPrompts.target} "${targetDir}"`
 
-            return `${dirForPrompt} is not empty. Remove existing files and continue?`
-          }
+            return `${dirForPrompt} ${language.shouldOverwrite.message}`
+          },
+          initial: true,
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'overwriteChecker',
           type: (prev, values) => {
             if (values.shouldOverwrite === false) {
-              throw new Error(red('✖') + ' Operation cancelled')
+              throw new Error(red('✖') + ` ${language.errors.operationCancelled}`)
             }
             return null
           }
@@ -174,73 +182,76 @@ async function init() {
         {
           name: 'packageName',
           type: () => (isValidPackageName(targetDir) ? null : 'text'),
-          message: 'Package name:',
+          message: language.packageName.message,
           initial: () => toValidPackageName(targetDir),
           validate: (dir) => isValidPackageName(dir) || 'Invalid package.json name'
         },
         {
           name: 'needsTypeScript',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add TypeScript?',
+          message: language.needsTypeScript.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsJsx',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add JSX Support?',
+          message: language.needsJsx.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsRouter',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add Vue Router for Single Page Application development?',
+          message: language.needsRouter.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsPinia',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add Pinia for state management?',
+          message: language.needsPinia.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsVitest',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add Vitest for Unit Testing?',
+          message: language.needsVitest.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsE2eTesting',
           type: () => (isFeatureFlagsUsed ? null : 'select'),
-          message: 'Add an End-to-End Testing Solution?',
+          message: language.needsE2eTesting.message,
           initial: 0,
           choices: (prev, answers) => [
-            { title: 'No', value: false },
             {
-              title: 'Cypress',
+              title: language.needsE2eTesting.selectOptions.negative.title,
+              value: false
+            },
+            {
+              title: language.needsE2eTesting.selectOptions.cypress.title,
               description: answers.needsVitest
                 ? undefined
-                : 'also supports unit testing with Cypress Component Testing',
+                : language.needsE2eTesting.selectOptions.cypress.desc,
               value: 'cypress'
             },
             {
-              title: 'Nightwatch',
+              title: language.needsE2eTesting.selectOptions.nightwatch.title,
               description: answers.needsVitest
                 ? undefined
-                : 'also supports unit testing with Nightwatch Component Testing',
+                : language.needsE2eTesting.selectOptions.nightwatch.desc,
               value: 'nightwatch'
             },
             {
-              title: 'Playwright',
+              title: language.needsE2eTesting.selectOptions.playwright.title,
               value: 'playwright'
             }
           ]
@@ -248,10 +259,10 @@ async function init() {
         {
           name: 'needsEslint',
           type: () => (isFeatureFlagsUsed ? null : 'toggle'),
-          message: 'Add ESLint for code quality?',
+          message: language.needsEslint.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         },
         {
           name: 'needsPrettier',
@@ -261,15 +272,15 @@ async function init() {
             }
             return 'toggle'
           },
-          message: 'Add Prettier for code formatting?',
+          message: language.needsPrettier.message,
           initial: false,
-          active: 'Yes',
-          inactive: 'No'
+          active: language.defaultToggleOptions.active,
+          inactive: language.defaultToggleOptions.inactive
         }
       ],
       {
         onCancel: () => {
-          throw new Error(red('✖') + ' Operation cancelled')
+          throw new Error(red('✖') + ` ${language.errors.operationCancelled}`)
         }
       }
     )
@@ -308,7 +319,7 @@ async function init() {
     fs.mkdirSync(root)
   }
 
-  console.log(`\nScaffolding project in ${root}...`)
+  console.log(`\n${language.infos.scaffolding} ${root}...`)
 
   const pkg = { name: packageName, version: '0.0.0' }
   fs.writeFileSync(path.resolve(root, 'package.json'), JSON.stringify(pkg, null, 2))
@@ -496,7 +507,7 @@ async function init() {
     })
   )
 
-  console.log(`\nDone. Now run:\n`)
+  console.log(`\n${language.infos.done}\n`)
   if (root !== cwd) {
     const cdProjectName = path.relative(cwd, root)
     console.log(
diff --git a/locales/en-US.json b/locales/en-US.json
new file mode 100644 (file)
index 0000000..7369c2e
--- /dev/null
@@ -0,0 +1,62 @@
+{
+  "projectName": {
+    "message": "Project name:"
+  },
+  "shouldOverwrite": {
+    "dirForPrompts": {
+      "current": "Current directory",
+      "target": "Target directory"
+    },
+    "message": "is not empty. Remove existing files and continue?"
+  },
+  "packageName": {
+    "message": "Package name:"
+  },
+  "needsTypeScript": {
+    "message": "Add TypeScript?"
+  },
+  "needsJsx": {
+    "message": "Add JSX Support?"
+  },
+  "needsRouter": {
+    "message": "Add Vue Router for Single Page Application development?"
+  },
+  "needsPinia": {
+    "message": "Add Pinia for state management?"
+  },
+  "needsVitest": {
+    "message": "Add Vitest for Unit Testing?"
+  },
+  "needsE2eTesting": {
+    "message": "Add an End-to-End Testing Solution?",
+    "selectOptions": {
+      "negative": { "title": "No" },
+      "cypress": {
+        "title": "Cypress",
+        "desc": "also supports unit testing with Cypress Component Testing"
+      },
+      "nightwatch": {
+        "title": "Nightwatch",
+        "desc": "also supports unit testing with Nightwatch Component Testing"
+      },
+      "playwright": { "title": "Playwright" }
+    }
+  },
+  "needsEslint": {
+    "message": "Add ESLint for code quality?"
+  },
+  "needsPrettier": {
+    "message": "Add Prettier for code formatting?"
+  },
+  "errors": {
+    "operationCancelled": "Operation cancelled"
+  },
+  "defaultToggleOptions": {
+    "active": "Yes",
+    "inactive": "No"
+  },
+  "infos": {
+    "scaffolding": "Scaffolding project in",
+    "done": "Done. Now run:"
+  }
+}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
new file mode 100644 (file)
index 0000000..6abe87a
--- /dev/null
@@ -0,0 +1,62 @@
+{
+  "projectName": {
+    "message": "请输入项目名称:"
+  },
+  "shouldOverwrite": {
+    "dirForPrompts": {
+      "current": "当前目录",
+      "target": "目标文件夹"
+    },
+    "message": "非空,是否覆盖?"
+  },
+  "packageName": {
+    "message": "请输入包名称:"
+  },
+  "needsTypeScript": {
+    "message": "是否使用 TypeScript 语法?"
+  },
+  "needsJsx": {
+    "message": "是否启用 JSX 支持?"
+  },
+  "needsRouter": {
+    "message": "是否引入 Vue Router 进行单页面应用开发?"
+  },
+  "needsPinia": {
+    "message": "是否引入 Pinia 用于状态管理?"
+  },
+  "needsVitest": {
+    "message": "是否引入 Vitest 用于单元测试?"
+  },
+  "needsE2eTesting": {
+    "message": "是否要引入一款端到端(End to End)测试工具?",
+    "selectOptions": {
+      "negative": { "title": "不需要" },
+      "cypress": {
+        "title": "Cypress",
+        "desc": "同时支持基于 Cypress Component Testing 的单元测试"
+      },
+      "nightwatch": {
+        "title": "Nightwatch",
+        "desc": "同时支持基于 Nightwatch Component Testing 的单元测试"
+      },
+      "playwright": { "title": "Playwright" }
+    }
+  },
+  "needsEslint": {
+    "message": "是否引入 ESLint 用于代码质量检测?"
+  },
+  "needsPrettier": {
+    "message": "是否引入 Prettier 用于代码格式化?"
+  },
+  "errors": {
+    "operationCancelled": "操作取消"
+  },
+  "defaultToggleOptions": {
+    "active": "是",
+    "inactive": "否"
+  },
+  "infos": {
+    "scaffolding": "正在构建项目",
+    "done": "项目构建完成,可执行以下命令:"
+  }
+}
index 6400f66e2b97a19a595883e8eaeb80633c37a1c6..e84b61f839650d2b1df5f3a1b74d2c948cab8ede 100644 (file)
@@ -7,6 +7,7 @@
     "create-vue": "outfile.cjs"
   },
   "files": [
+    "locales",
     "outfile.cjs",
     "template"
   ],
index adca4c6e99e584c60238f0f0b0f5b5d63bb54b9f..f1d780beee1c36d9adabe34db609b5a9b043875b 100644 (file)
@@ -27,6 +27,7 @@ SOFTWARE.
 await esbuild.build({
   bundle: true,
   entryPoints: ['index.ts'],
+  external: ['locales/*'],
   outfile: 'outfile.cjs',
   format: 'cjs',
   platform: 'node',
diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts
new file mode 100644 (file)
index 0000000..537439b
--- /dev/null
@@ -0,0 +1,70 @@
+import * as fs from 'node:fs'
+import * as path from 'node:path'
+
+interface LanguageItem {
+  message: string
+  dirForPrompts?: {
+    current: string
+    target: string
+  }
+  toggleOptions?: {
+    active: string
+    inactive: string
+  }
+  selectOptions?: {
+    [key: string]: { title: string; desc?: string }
+  }
+}
+
+interface Language {
+  projectName: LanguageItem
+  shouldOverwrite: LanguageItem
+  packageName: LanguageItem
+  needsTypeScript: LanguageItem
+  needsJsx: LanguageItem
+  needsRouter: LanguageItem
+  needsPinia: LanguageItem
+  needsVitest: LanguageItem
+  needsE2eTesting: LanguageItem
+  needsEslint: LanguageItem
+  needsPrettier: LanguageItem
+  errors: {
+    operationCancelled: string
+  }
+  defaultToggleOptions: {
+    active: string
+    inactive: string
+  }
+  infos: {
+    scaffolding: string
+    done: string
+  }
+}
+
+function getLocale() {
+  const shellLocale =
+    process.env.LC_ALL ||
+    process.env.LANG || // Unix maybe
+    process.env.LC_CTYPE || // C libraries maybe
+    process.env.LANGSPEC || // Windows maybe
+    Intl.DateTimeFormat().resolvedOptions().locale || // Node.js - Internationalization support
+    'en-US'
+
+  const locale = shellLocale.split('.')[0].replace('_', '-')
+
+  // locale might be 'C' or something else
+  return locale.length < 5 ? 'en-US' : locale
+}
+
+export default function getLanguage() {
+  const locale = getLocale()
+  const localesRoot = path.resolve(__dirname, 'locales')
+  const languageFilePath = path.resolve(localesRoot, `${locale}.json`)
+  const doesLanguageExist = fs.existsSync(languageFilePath)
+
+  const lang: Language = doesLanguageExist
+    ? require(languageFilePath)
+    : require(path.resolve(localesRoot, 'en-US.json'))
+
+  return lang
+}