generator-jhipster
Version:
Spring Boot + Angular/React/Vue in one handy generator
166 lines (165 loc) • 7.33 kB
JavaScript
/**
* 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;