From 6c812671e2d1f47485b35b96b658e199059f9a3a Mon Sep 17 00:00:00 2001 From: "yordan.ramchev" Date: Fri, 1 Dec 2023 20:43:53 +0200 Subject: [PATCH 1/3] feat: add localization prompt re #96 --- index.ts | 96 +++++++++++++++++++++++--------------------- locales/en-US.json | 64 +++++++++++++++++++++++++++++ locales/fr-FR.json | 64 +++++++++++++++++++++++++++++ locales/zh-CN.json | 64 +++++++++++++++++++++++++++++ package.json | 1 + scripts/build.mjs | 1 + utils/getLanguage.ts | 81 +++++++++++++++++++++++++++++++++++++ 7 files changed, 326 insertions(+), 45 deletions(-) create mode 100644 locales/en-US.json create mode 100644 locales/fr-FR.json create mode 100644 locales/zh-CN.json create mode 100644 utils/getLanguage.ts diff --git a/index.ts b/index.ts index e3f9d315..a31f568e 100755 --- a/index.ts +++ b/index.ts @@ -16,6 +16,7 @@ import { postOrderDirectoryTraverse, preOrderDirectoryTraverse } from './utils/d import generateReadme from './utils/generateReadme' import generateIndex from './utils/generateIndex' import getCommand from './utils/getCommand' +import getLanguage from './utils/getLanguage' import renderEslint from './utils/renderEslint' import { FILES_TO_FILTER } from './utils/filterList' @@ -118,7 +119,7 @@ async function init() { const defaultProjectName = !targetDir ? 'vue-project' : targetDir const forceOverwrite = argv.force - + const language = getLanguage() let result: { projectName?: string shouldOverwrite?: boolean @@ -158,25 +159,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 : 'contogglefirm'), 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 } @@ -184,73 +190,73 @@ 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' + validate: (dir) => isValidPackageName(dir) || language.packageName.invalidMessage }, { 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?', + hint: language.needsE2eTesting.hint, + message: language.needsE2eTesting.message, initial: 0, choices: (prev, answers) => [ - { title: 'No', value: false }, + { title: language.needsE2eTesting.selectOptions.negative.title, value: false }, { - title: 'Playwright', + title: language.needsE2eTesting.selectOptions.playwright.title, value: 'playwright' }, { - title: 'Nightwatch', + title: language.needsE2eTesting.selectOptions.nightwatch.title, description: answers.needsVitest ? undefined - : 'also supports unit testing with Nightwatch Component Testing', - value: 'nightwatch' + : language.needsE2eTesting.selectOptions.nightwatch.desc }, { - title: 'Cypress', + 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' } ] @@ -260,24 +266,24 @@ async function init() { type: () => (isFeatureFlagsUsed ? null : 'toggle'), message: 'Add VueUse - Collection of essential Composition Utilities?', initial: false, - active: 'Yes', - inactive: 'No' + active: language.defaultToggleOptions.active, + inactive: language.defaultToggleOptions.inactive }, { name: 'needsI18n', type: () => (isFeatureFlagsUsed ? null : 'toggle'), message: 'Add i18n - internationalization plugin?', initial: false, - active: 'Yes', - inactive: 'No' + active: language.defaultToggleOptions.active, + inactive: language.defaultToggleOptions.inactive }, { name: 'needsStorybook', type: () => (isFeatureFlagsUsed ? null : 'toggle'), message: 'Add Storybook?', initial: false, - active: 'Yes', - inactive: 'No' + active: language.defaultToggleOptions.active, + inactive: language.defaultToggleOptions.inactive }, { name: 'needsSonarQube', @@ -289,8 +295,8 @@ async function init() { }, message: 'Add SonarQube for code coverage?', initial: false, - active: 'Yes', - inactive: 'No' + active: language.defaultToggleOptions.active, + inactive: language.defaultToggleOptions.inactive }, { name: 'needsTanStackQuery', @@ -298,13 +304,13 @@ async function init() { message: 'Add TanStack Query - Hooks for fetching, caching and updating asynchronous data?', 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}`) } } ) @@ -346,7 +352,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)) @@ -552,7 +558,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 index 00000000..19a650ae --- /dev/null +++ b/locales/en-US.json @@ -0,0 +1,64 @@ +{ + "projectName": { + "message": "Project name:", + "invalidMessage": "Invalid package.json 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?", + "hint": "- Use arrow-keys. Return to submit.", + "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/fr-FR.json b/locales/fr-FR.json new file mode 100644 index 00000000..7f5f2223 --- /dev/null +++ b/locales/fr-FR.json @@ -0,0 +1,64 @@ +{ + "projectName": { + "message": "Nom du projet\u00a0:", + "invalidMessage": "Le nom du package.json est invalide" + }, + "shouldOverwrite": { + "dirForPrompts": { + "current": "Répertoire courant", + "target": "Répertoire cible" + }, + "message": "n'est pas vide. Supprimer les fichiers existants et continuer\u00a0?" + }, + "packageName": { + "message": "Nom du package\u00a0:" + }, + "needsTypeScript": { + "message": "Ajouter TypeScript\u00a0?" + }, + "needsJsx": { + "message": "Ajouter le support de JSX\u00a0?" + }, + "needsRouter": { + "message": "Ajouter Vue Router pour le développement d'applications _single page_\u00a0?" + }, + "needsPinia": { + "message": "Ajouter Pinia pour la gestion de l'état\u00a0?" + }, + "needsVitest": { + "message": "Ajouter Vitest pour les tests unitaires\u00a0?" + }, + "needsE2eTesting": { + "message": "Ajouter une solution de test de bout en bout (e2e)\u00a0?", + "hint": "- Utilisez les flèches et appuyez sur la touche Entrée pour valider", + "selectOptions": { + "negative": { "title": "Non" }, + "cypress": { + "title": "Cypress", + "desc": "prend également en charge les tests unitaires avec Cypress Component Testing" + }, + "nightwatch": { + "title": "Nightwatch", + "desc": "prend également en charge les tests unitaires avec Nightwatch Component Testing" + }, + "playwright": { "title": "Playwright" } + } + }, + "needsEslint": { + "message": "Ajouter ESLint pour la qualité du code\u00a0?" + }, + "needsPrettier": { + "message": "Ajouter Prettier pour le formatage du code\u00a0?" + }, + "errors": { + "operationCancelled": "Operation annulée" + }, + "defaultToggleOptions": { + "active": "Oui", + "inactive": "Non" + }, + "infos": { + "scaffolding": "Génération du projet dans", + "done": "Terminé. Exécutez maintenant\u00a0:" + } +} diff --git a/locales/zh-CN.json b/locales/zh-CN.json new file mode 100644 index 00000000..98144010 --- /dev/null +++ b/locales/zh-CN.json @@ -0,0 +1,64 @@ +{ + "projectName": { + "message": "请输入包名称:", + "invalidMessage": "无效的 package.json 名称" + }, + "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)测试工具?", + "hint": "- 使用箭头切换按Enter确认。", + "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": "项目构建完成,可执行以下命令:" + } +} diff --git a/package.json b/package.json index b3ac9f03..91427c85 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "create-vue": "outfile.cjs" }, "files": [ + "locales", "outfile.cjs", "template" ], diff --git a/scripts/build.mjs b/scripts/build.mjs index adca4c6e..f1d780be 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -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 index 00000000..aa838295 --- /dev/null +++ b/utils/getLanguage.ts @@ -0,0 +1,81 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' + +interface LanguageItem { + message: string + hint?: string + invalidMessage?: 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 = + Intl.DateTimeFormat().resolvedOptions().locale || // Built-in ECMA-402 support + process.env.LC_ALL || // POSIX locale environment variables + process.env.LC_MESSAGES || + process.env.LANG || + // TODO: Windows support if needed, could consider https://www.npmjs.com/package/os-locale + 'en-US' // Default fallback + + const locale = shellLocale.split('.')[0].replace('_', '-') + + // locale might be 'C' or something else + return locale +} + +export default function getLanguage() { + const locale = getLocale() + // Note here __dirname would not be transpiled, + // so it refers to the __dirname of the file `/outfile.cjs` + // TODO: use glob import once https://github.com/evanw/esbuild/issues/3320 is fixed + const localesRoot = path.resolve(__dirname, 'locales') + const languageFilePath = path.resolve(localesRoot, `${locale}.json`) + const doesLanguageExist = fs.existsSync(languageFilePath) + + if (!doesLanguageExist) { + console.warn( + `\x1B[33mThe locale langage "${locale}" is not supported, fallback to "en-US".\n\x1B[39m` + ) + } + + const lang: Language = doesLanguageExist + ? require(languageFilePath) + : require(path.resolve(localesRoot, 'en-US.json')) + + return lang +} From bc93bac016785414a65017c7d5eb94f6495eb2d2 Mon Sep 17 00:00:00 2001 From: "yordan.ramchev" Date: Tue, 5 Dec 2023 13:20:53 +0200 Subject: [PATCH 2/3] fix: function typo --- index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index a31f568e..634656c4 100755 --- a/index.ts +++ b/index.ts @@ -165,7 +165,7 @@ async function init() { }, { name: 'shouldOverwrite', - type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'contogglefirm'), + type: () => (canSkipEmptying(targetDir) || forceOverwrite ? null : 'toggle'), message: () => { const dirForPrompt = targetDir === '.' @@ -250,7 +250,8 @@ async function init() { title: language.needsE2eTesting.selectOptions.nightwatch.title, description: answers.needsVitest ? undefined - : language.needsE2eTesting.selectOptions.nightwatch.desc + : language.needsE2eTesting.selectOptions.nightwatch.desc, + value: 'nightwatch' }, { title: language.needsE2eTesting.selectOptions.cypress.title, From 826b3c31aebae96e0f15f5812a2fe51e0ec95567 Mon Sep 17 00:00:00 2001 From: "yordan.ramchev" Date: Wed, 6 Dec 2023 11:36:09 +0200 Subject: [PATCH 3/3] feat: turkish localization for prompts messages --- locales/tr-TR.json | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 locales/tr-TR.json diff --git a/locales/tr-TR.json b/locales/tr-TR.json new file mode 100644 index 00000000..3b97c8b8 --- /dev/null +++ b/locales/tr-TR.json @@ -0,0 +1,64 @@ +{ + "projectName": { + "message": "Proje adı:" + }, + "shouldOverwrite": { + "dirForPrompts": { + "current": "Geçerli dizin", + "target": "Hedef dizin" + }, + "message": "boş değil. Varolan dosyalar silinip devam edilsin mi?" + }, + "packageName": { + "message": "Paket adı:", + "invalidMessage": "Geçersiz package.json adı" + }, + "needsTypeScript": { + "message": "TypeScript Eklensin mi?" + }, + "needsJsx": { + "message": "JSX Desteği Eklensin mi?" + }, + "needsRouter": { + "message": "Tek Sayfa Uygulama geliştirilmesi için Vue Router eklensin mi?" + }, + "needsPinia": { + "message": "Durum yönetimi için Pinia eklensin mi?" + }, + "needsVitest": { + "message": "Birim Testi için Vitest eklensin mi?" + }, + "needsE2eTesting": { + "message": "Uçtan Uca Test Çözümü Eklensin mi?", + "hint": "- Ok tuşlarını kullan. Gönderime geri dön.", + "selectOptions": { + "negative": { "title": "Hayır" }, + "cypress": { + "title": "Cypress", + "desc": "ayrıca Cypress Bileşen Testi ile birim testini de destekler" + }, + "nightwatch": { + "title": "Nightwatch", + "desc": "ayrıca Nightwatch Bileşen Testi ile birim testini de destekler" + }, + "playwright": { "title": "Playwright" } + } + }, + "needsEslint": { + "message": "Kod kalitesi için ESLint eklensin mi?" + }, + "needsPrettier": { + "message": "Kod formatlama için Prettier eklensin mi?" + }, + "errors": { + "operationCancelled": "İşlem iptal edildi" + }, + "defaultToggleOptions": { + "active": "Evet", + "inactive": "Hayır" + }, + "infos": { + "scaffolding": "İskele projesi", + "done": "Tamamlandı. Şimdi bunu çalıştır:" + } +}