UNPKG

generator-jhipster

Version:

Spring Boot + Angular/React/Vue in one handy generator

166 lines (165 loc) 7.33 kB
/** * Copyright 2013-2026 the original author or authors from the JHipster project. * * This file is part of the JHipster project, see https://www.jhipster.tech/ * for more information. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { passthrough } from '@yeoman/transform'; import { Minimatch } from 'minimatch'; function replaceTranslationAttributes({ content, getWebappTranslation, }) { return content.replaceAll(/:(?<tag>(?:placeholder|title|label))="(?<translate>t\$\([^"]+\))"/g, (_complete, ...args) => { const groups = args.pop(); if (groups.translate.includes('+')) { return ''; } const translated = replaceTranslations({ content: groups.translate, type: 'vue', getWebappTranslation }); return `${groups.tag}="${translated}"`; }); } export function removeDeclarations({ content }) { return content .replaceAll(/\nimport {\s*useI18n\s*} from 'vue-i18n';/g, '') .replaceAll(/\n\s*t\$,/g, '') .replaceAll(/\n\s*t\$:\s*useI18n\(\).t,/g, ''); } export function replaceVueTranslations({ body, enableTranslation, getWebappTranslation, }) { body = body .replaceAll(/{{ *(?<translate>t\$\([^)]*\)) *}}/g, (complete, ...args) => { const groups = args.pop(); if (!enableTranslation && groups.translate) { if (groups.translate.includes('+')) { this?.log.info(`Ignoring dynamic translation ${groups.translate}`); } else { const translated = replaceTranslations({ type: 'vue', content: groups.translate, getWebappTranslation, }); if (translated !== groups.translate) { return translated; } throw new Error(`${translated}, ${groups.translate}`); } } else if (groups.translate) { // Normalize white spaces return `{{ ${groups.translate} }}`; } return complete; }) .replaceAll(/(?<tagWithAttributes>(?<beforeTranslateTag><(?<tagName>a|b-(?:badge|link|button|alert)|button|div|h[1-9]|input|label|p|router-link|small|span|t[hd])(?:[^>]*)) v-(?:text|html)="(?<translate>t\$\([^)]*\))"(?<afterTranslateTag>(?:(?!\/?>)[^/>])*>))(?<tagContent>(?:(?!<\/\k<tagName>(?:>|\s|\n)).|\n)*)/g, (_complete, ...args) => { const groups = args.pop(); if (new RegExp(String.raw `<${groups.tagName}[\s>]`, 'g').test(groups.tagContent)) { throw new Error(`Nested tags identical to the translated tag are not supported: ${groups.tagWithAttributes}${groups.tagContent}`); } if (enableTranslation) { return groups.tagWithAttributes; } if (groups.translate) { if (groups.translate.includes('+')) { this?.log.info(`Ignoring dynamic translation ${groups.translate}`); } else { const translated = replaceTranslations({ type: 'vue', content: groups.translate, getWebappTranslation, }); if (translated !== groups.translate) { return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${translated}`; } throw new Error(`${translated}, ${groups.translate}`); } } return `${groups.beforeTranslateTag}${groups.afterTranslateTag}${groups.tagContent}`; }); if (enableTranslation) { return body; } return replaceTranslationAttributes({ content: body, getWebappTranslation }); } export function replaceTranslations({ content, type, getWebappTranslation, }) { const regex = type === 'ts' ? /(?:this.)?(t\$|i18n.t)\((?<key>[^),]*)(?:,\s*{(?<data>[^)]*)})?\)\.toString\(\)/g : /t\$\((?<key>[^),]*)(?:,\s*{(?<data>[^)]*)})?\)/g; return content.replaceAll(regex, (_complete, ...args) => { const groups = args.pop(); const key = groups.key.slice(1, -1).replaceAll(String.raw `\'`, "'"); let data; if (groups.data) { const interpolateMatches = groups.data.matchAll(/(?<field>[^{\s:,}]+)(?:\s*:\s*(?<value>[^,}]+))?/g); data = {}; let templateLiteral = false; for (const interpolateMatch of interpolateMatches) { let { field, value } = interpolateMatch.groups || {}; if (/^'.*'$/.test(field) || /^".*"$/.test(field)) { // unwrap field field = field.slice(1, -1); } value ??= field; value = value.trim(); if (/^\d+$/.test(value)) { // convert integer value = Number.parseInt(value, 10); } else if (/^'.*'$/.test(value) || /^".*"$/.test(value)) { // extract string value value = value.substring(1, value.length - 1); } else { // wrap expression if (type === 'vue') { value = `{{ ${value} }}`; } else { value = `\${${value}}`; } templateLiteral = true; } data[field] = value; } if (templateLiteral && type === 'ts') { return `\`${getWebappTranslation(key, data)}\``; } } if (type === 'vue') { return getWebappTranslation(key, data); } return `'${getWebappTranslation(key, data)}'`; }); } const minimatch = new Minimatch('**/*.{vue,ts}'); export const isTranslatedVueFile = (file) => minimatch.match(file.path); function translateVueFilesTransform({ enableTranslation, getWebappTranslation, }) { return passthrough(file => { let newContent; if (file.path.endsWith('.vue')) { newContent = replaceVueTranslations.call(this, { body: file.contents.toString(), enableTranslation, getWebappTranslation }); } else if (!enableTranslation && file.path.endsWith('.ts')) { newContent = replaceTranslations({ type: 'ts', content: file.contents.toString(), getWebappTranslation }); newContent = removeDeclarations({ content: newContent }); } if (newContent) { if (!enableTranslation && /(?<![A-Za-z0-9])t\$/.test(newContent)) { throw new Error(`Could not translate ${file.path}: ${newContent}`); } file.contents = Buffer.from(newContent); } }); } export default translateVueFilesTransform;