Skip to content

Commit

Permalink
add simplified and improved translation process.
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed Mar 28, 2024
1 parent 510ed68 commit a73b584
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 51 deletions.
3 changes: 2 additions & 1 deletion generators/angular/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,14 @@ export default class AngularGenerator extends BaseApplicationGenerator {
this.localEntities = entities.filter(entity => !entity.builtIn && !entity.skipClient);
},
queueTranslateTransform({ control, application }) {
const { enableTranslation, jhiPrefix } = application;
this.queueTransformStream(
{
name: 'translating angular application',
filter: file => isFileStateModified(file) && file.path.startsWith(this.destinationPath()) && isTranslatedAngularFile(file),
refresh: false,
},
translateAngularFilesTransform(control.getWebappTranslation, application.enableTranslation),
translateAngularFilesTransform(control.getWebappTranslation, { enableTranslation, jhiPrefix }),
);
},
});
Expand Down
1 change: 0 additions & 1 deletion generators/angular/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@ export * from './needles.js';
export * from './path-utils.js';
export * from './reserved-keywords.js';
export * from './translate-angular.js';
export { default as translateAngularFilesTransform } from './translate-angular.js';
export { default as updateLanguagesTask } from './update-languages.js';
78 changes: 73 additions & 5 deletions generators/angular/support/translate-angular.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ import { createTranslationReplacer } from './translate-angular.js';
describe('generator - angular - transform', () => {
describe('replaceAngularTranslations', () => {
let replaceAngularTranslations;
let enabledAngularTranslations;

beforeEach(() => {
let value = 0;
replaceAngularTranslations = createTranslationReplacer(esmocha.fn().mockImplementation(key => `translated-value-${key}-${value++}`));
replaceAngularTranslations = createTranslationReplacer(
esmocha.fn().mockImplementation(key => `translated-value-${key}-${value++}`),
{ jhiPrefix: 'jhi', enableTranslation: false },
);
enabledAngularTranslations = createTranslationReplacer(
esmocha.fn().mockImplementation(key => `translated-value-${key}-${value++}`),
{ jhiPrefix: 'jhi', enableTranslation: true },
);
});

describe('with translation disabled', () => {
Expand All @@ -39,8 +47,8 @@ describe('generator - angular - transform', () => {
`;
expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(`
"
<h1>translated-value-activate.title1-0</h1>
<h1>translated-value-activate.title2-1</h1>
<h1>translated-value-activate.title1-1</h1>
<h1>translated-value-activate.title2-0</h1>
"
`);
});
Expand Down Expand Up @@ -110,7 +118,7 @@ describe('generator - angular - transform', () => {
`);
});

it('should remove placeholder attribute value with translated value', () => {
it('should replace placeholder attribute value with translated value', () => {
const body = `
<input placeholder="{{ 'global.form.currentpassword.placeholder1' | translate }}"/>
<input placeholder="{{ 'global.form.currentpassword.placeholder2' | translate }}"/>
Expand All @@ -123,7 +131,7 @@ describe('generator - angular - transform', () => {
`);
});

it('should remove title attribute value with translated value', () => {
it('should replace title attribute value with translated value', () => {
const body = `
<input title="{{ 'global.form.currentpassword.title1' | translate }}"/>
<input title="{{ 'global.form.currentpassword.title2' | translate }}"/>
Expand All @@ -133,6 +141,66 @@ describe('generator - angular - transform', () => {
<input title="translated-value-global.form.currentpassword.title1-0"/>
<input title="translated-value-global.form.currentpassword.title2-1"/>
"
`);
});

it('should replace __jhiTranslatePipe__ with translated value', () => {
const body = `
<input title="__jhiTranslatePipe__('global.form.currentpassword.title1')"/>
<input title="__jhiTranslatePipe__('global.form.currentpassword.title2')"/>
`;
expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(`
"
<input title="translated-value-global.form.currentpassword.title1-1"/>
<input title="translated-value-global.form.currentpassword.title2-0"/>
"
`);
});

it('should replace __jhiTranslatePipe__ with translation pipe', () => {
const body = `
<input title="__jhiTranslatePipe__('global.form.currentpassword.title1')"/>
<input title="__jhiTranslatePipe__('global.form.currentpassword.title2')"/>
`;
expect(enabledAngularTranslations(body, extension)).toMatchInlineSnapshot(`
"
<input title="{{ 'global.form.currentpassword.title1' | translate }}"/>
<input title="{{ 'global.form.currentpassword.title2' | translate }}"/>
"
`);
});

it('should replace __jhiTranslateTag__ translated value', () => {
const body = `
<tag>__jhiTranslateTag__('global.form.currentpassword.title1')</tag>
<tag>
__jhiTranslateTag__('global.form.currentpassword.title2')
</tag>
`;
expect(replaceAngularTranslations(body, extension)).toMatchInlineSnapshot(`
"
<tag>translated-value-global.form.currentpassword.title1-1</tag>
<tag>
translated-value-global.form.currentpassword.title2-0
</tag>
"
`);
});

it('should replace __jhiTranslateTag__ with translation attribute and value', () => {
const body = `
<tag>__jhiTranslateTag__('global.form.currentpassword.title1')</tag>
<tag>
__jhiTranslateTag__('global.form.currentpassword.title2')
</tag>
`;
expect(enabledAngularTranslations(body, extension)).toMatchInlineSnapshot(`
"
<tag jhiTranslate="global.form.currentpassword.title1">translated-value-global.form.currentpassword.title1-1</tag>
<tag jhiTranslate="global.form.currentpassword.title2">
translated-value-global.form.currentpassword.title2-0
</tag>
"
`);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@
import { passthrough } from '@yeoman/transform';
import { Minimatch } from 'minimatch';

import { createJhiTransformTranslateReplacer, createJhiTransformTranslateStringifyReplacer } from '../../languages/support/index.js';
import {
type JHITranslateConverterOptions,
createJhiTransformTranslateReplacer,
createJhiTransformTranslateStringifyReplacer,
createJhiTranslateReplacer,
escapeTranslationValue,
} from '../../languages/support/index.js';

const PLACEHOLDER_REGEX = /(?:placeholder|title)=['|"](\{\{\s?['|"]([a-zA-Z0-9.\-_]+)['|"]\s?\|\s?translate\s?\}\})['|"]/.source;

const JHI_TRANSLATE_REGEX = /(\n?\s*[a-z][a-zA-Z]*Translate="[a-zA-Z0-9 +{}'_!?.]+")/.source;
const TRANSLATE_VALUES_REGEX = /(\n?\s*\[translateValues\]="\{(?:(?!\}").)*?\}")/.source;
const TRANSLATE_REGEX = [JHI_TRANSLATE_REGEX, TRANSLATE_VALUES_REGEX].join('|');

function getTranslationValue(getWebappTranslation, key, data) {
export type ReplacerOptions = { jhiPrefix: string; enableTranslation: boolean };

function getTranslationValue(getWebappTranslation, key, data?) {
return getWebappTranslation(key, data) || undefined;
}

Expand All @@ -40,9 +48,19 @@ function getTranslationValue(getWebappTranslation, key, data) {
* @param {object} [options]
* @param {number} [options.keyIndex]
* @param {number} [options.replacementIndex]
* @param {any} [options.escape]
* @returns {string}
*/
function replaceTranslationKeysWithText(getWebappTranslation, content, regexSource, { keyIndex = 1, replacementIndex = 1, escape } = {}) {
function replaceTranslationKeysWithText(
getWebappTranslation,
content,
regexSource,
{
keyIndex = 1,
replacementIndex = 1,
escape,
}: { keyIndex?: number; replacementIndex?: number; escape?: (str: string, match: any) => string } = {},
) {
const regex = new RegExp(regexSource, 'g');
const allMatches = content.matchAll(regex);
for (const match of allMatches) {
Expand Down Expand Up @@ -102,15 +120,75 @@ function replaceErrorMessage(getWebappTranslation, content) {
return replaceJSTranslation(getWebappTranslation, content, 'errorMessage');
}

/**
* Generate the `translateValues` attribute.
*/
const translateValues = (parsedInterpolate: Record<string, string>) =>
` [translateValues]="{ ${Object.entries(parsedInterpolate)
.map(([key, value]) => `${key}: ${value}`)
.join(',')} }"`;

/**
* Convert interpolation values to angular template for later processing.
*/
const translationValueInterpolate = (parsedInterpolate: Record<string, string>): Record<string, string> =>
Object.fromEntries(Object.entries(parsedInterpolate).map(([key, value]) => [key, `{{ ${value} }}`]));

/**
* Creates a `jhiTranslate` attribute with optional translateValues.
* Or the translation value if translation is disabled.
*/
const tagTranslation = (
getWebappTranslation: any,
{ enableTranslation, jhiPrefix }: ReplacerOptions,
{ key, parsedInterpolate, prefix, suffix }: JHITranslateConverterOptions,
) => {
if (enableTranslation) {
return ` ${jhiPrefix}Translate="${key}"${
parsedInterpolate ? translateValues(parsedInterpolate) : ''
}${prefix}${escapeTranslationValue(getTranslationValue(getWebappTranslation, key, parsedInterpolate ? translationValueInterpolate(parsedInterpolate) : undefined))}${suffix}`;
}

return `${prefix}${escapeTranslationValue(getTranslationValue(getWebappTranslation, key, parsedInterpolate))}${suffix}`;
};

/**
* Creates a `translate` pipe.
* Or the translation value if translation is disabled.
*/
const pipeTranslation = (getWebappTranslation: any, { enableTranslation }: ReplacerOptions, { key }: JHITranslateConverterOptions) => {
if (enableTranslation) {
return `{{ '${key}' | translate }}`;
}

return `${escapeTranslationValue(getTranslationValue(getWebappTranslation, key))}`;
};

/**
* Replace and cleanup translations.
*
* @type {import('../generator-base.js').EditFileCallback}
* @this {import('../generator-base.js')}
*/
export const createTranslationReplacer = (getWebappTranslation, enableTranslation) => {
export const createTranslationReplacer = (getWebappTranslation, opts: ReplacerOptions | boolean) => {
const htmlJhiTranslateReplacer = createJhiTransformTranslateReplacer(getWebappTranslation, { escapeHtml: true });
const htmlJhiTranslateStringifyReplacer = createJhiTransformTranslateStringifyReplacer(getWebappTranslation, { escapeHtml: true });
const htmlJhiTranslateStringifyReplacer = createJhiTransformTranslateStringifyReplacer(getWebappTranslation);
let translationReplacer: ((content: string) => string) | undefined;
const enableTranslation = typeof opts === 'boolean' ? opts : opts.enableTranslation;
if (typeof opts !== 'boolean') {
translationReplacer = createJhiTranslateReplacer(
optsReplacer => {
if (optsReplacer.type === 'Tag') {
return tagTranslation(getWebappTranslation, opts, optsReplacer);
}
if (optsReplacer.type === 'Pipe') {
return pipeTranslation(getWebappTranslation, opts, optsReplacer);
}
throw new Error(`Translation type not supported ${optsReplacer.type}`);
},
{ prefixPattern: '>\\s*', suffixPattern: '\\s*<' },
);
}
return function replaceAngularTranslations(content, filePath) {
if (/\.html$/.test(filePath)) {
if (!enableTranslation) {
Expand All @@ -122,6 +200,7 @@ export const createTranslationReplacer = (getWebappTranslation, enableTranslatio
if (/(:?\.html|component\.ts)$/.test(filePath)) {
content = htmlJhiTranslateReplacer(content);
content = htmlJhiTranslateStringifyReplacer(content);
content = translationReplacer?.(content);
}
if (!enableTranslation) {
if (/(:?route|module)\.ts$/.test(filePath)) {
Expand All @@ -138,11 +217,9 @@ export const createTranslationReplacer = (getWebappTranslation, enableTranslatio
const minimatch = new Minimatch('**/*{.html,.component.ts,.route.ts,.module.ts}');
export const isTranslatedAngularFile = file => minimatch.match(file.path);

const translateAngularFilesTransform = (getWebappTranslation, enableTranslation) => {
const translate = createTranslationReplacer(getWebappTranslation, enableTranslation);
export const translateAngularFilesTransform = (getWebappTranslation, opts: ReplacerOptions | boolean) => {
const translate = createTranslationReplacer(getWebappTranslation, opts);
return passthrough(file => {
file.contents = Buffer.from(translate(file.contents.toString(), file.path));
});
};

export default translateAngularFilesTransform;
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,44 @@
</div>

<div class="col-md-9">
<h1 class="display-4"><span <%= jhiPrefix %>Translate="home.title">__jhiTransformTranslate__('home.title')</span> (<%= humanizedBaseName %>)</h1>
<h1 class="display-4"><span>__jhiTranslateTag__('home.title')</span> (<%= humanizedBaseName %>)</h1>

<p class="lead" <%= jhiPrefix %>Translate="home.subtitle">__jhiTransformTranslate__('home.subtitle')</p>
<p class="lead">__jhiTranslateTag__('home.subtitle')</p>

<div>
@if (account() !== null) {
<div class="alert alert-success">
@if (account()) {
<span id="home-logged-message" <%= jhiPrefix %>Translate="home.logged.message"
[translateValues]="{ username: account()!.login }">__jhiTransformTranslate__('home.logged.message', { "username": "{{ account()!.login }}" })</span>
<span id="home-logged-message">__jhiTranslateTag__('home.logged.message', { "username": "account()!.login" })</span>
}
</div>
} @else {
<div class="alert alert-warning">
<span <%= jhiPrefix %>Translate="global.messages.info.authenticated.prefix">__jhiTransformTranslate__('global.messages.info.authenticated.prefix')</span>
<a class="alert-link" (click)="login()" <%= jhiPrefix %>Translate="global.messages.info.authenticated.link">__jhiTransformTranslate__('global.messages.info.authenticated.link')</a><span <%= jhiPrefix %>Translate="global.messages.info.authenticated.suffix">__jhiTransformTranslate__('global.messages.info.authenticated.suffix')</span>
<span>__jhiTranslateTag__('global.messages.info.authenticated.prefix')</span>
<a class="alert-link" (click)="login()">__jhiTranslateTag__('global.messages.info.authenticated.link')</a><span>__jhiTranslateTag__('global.messages.info.authenticated.suffix')</span>
</div>
<%_ if (generateUserManagement) { _%>
<div class="alert alert-warning">
<span <%= jhiPrefix %>Translate="global.messages.info.register.noaccount">__jhiTransformTranslate__('global.messages.info.register.noaccount')</span>&nbsp;
<a class="alert-link" routerLink="/account/register" <%= jhiPrefix %>Translate="global.messages.info.register.link">__jhiTransformTranslate__('global.messages.info.register.link')</a>
<span>__jhiTranslateTag__('global.messages.info.register.noaccount')</span>&nbsp;
<a class="alert-link" routerLink="/account/register">__jhiTranslateTag__('global.messages.info.register.link')</a>
</div>
<%_ } _%>
}
</div>

<p <%= jhiPrefix %>Translate="home.question">__jhiTransformTranslate__('home.question')</p>
<p>__jhiTranslateTag__('home.question')</p>

<ul>
<li><a href="https://www.jhipster.tech/" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.link.homepage">__jhiTransformTranslate__('home.link.homepage')</a></li>
<li><a href="https://stackoverflow.com/tags/jhipster/info" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.link.stackoverflow">__jhiTransformTranslate__('home.link.stackoverflow')</a></li>
<li><a href="https://github.com/jhipster/generator-jhipster/issues?state=open" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.link.bugtracker">__jhiTransformTranslate__('home.link.bugtracker')</a></li>
<li><a href="https://gitter.im/jhipster/generator-jhipster" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.link.chat">__jhiTransformTranslate__('home.link.chat')</a></li>
<li><a href="https://twitter.com/jhipster" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.link.follow">__jhiTransformTranslate__('home.link.follow')</a></li>
<li><a href="https://www.jhipster.tech/" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.link.homepage')</a></li>
<li><a href="https://stackoverflow.com/tags/jhipster/info" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.link.stackoverflow')</a></li>
<li><a href="https://github.com/jhipster/generator-jhipster/issues?state=open" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.link.bugtracker')</a></li>
<li><a href="https://gitter.im/jhipster/generator-jhipster" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.link.chat')</a></li>
<li><a href="https://twitter.com/jhipster" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.link.follow')</a></li>
</ul>

<p>
<span <%= jhiPrefix %>Translate="home.like">__jhiTransformTranslate__('home.like')</span> <a href="https://github.com/jhipster/generator-jhipster" target="_blank" rel="noopener noreferrer" <%= jhiPrefix %>Translate="home.github">__jhiTransformTranslate__('home.github')</a>!
<span>__jhiTranslateTag__('home.like')</span> <a href="https://github.com/jhipster/generator-jhipster" target="_blank" rel="noopener noreferrer">__jhiTranslateTag__('home.github')</a>!
</p>
</div>
</div>
Loading

0 comments on commit a73b584

Please sign in to comment.