import express from 'express'; import { dirname, resolve } from 'node:path'; import { getGlobalVariable } from '../../utils/env'; import { appendToFile, copyFile, createDir, replaceInFile, writeFile } from '../../utils/fs'; import { installPackage } from '../../utils/packages'; import { updateJsonFile } from '../../utils/project'; import { readNgVersion } from '../../utils/version'; import { Server } from 'node:http'; import { AddressInfo } from 'node:net'; import type { Page } from 'puppeteer'; // Configurations for each locale. const translationFile = 'src/locale/messages.xlf'; export const baseDir = 'dist/test-project/browser'; export const langTranslations = [ { lang: 'en-US', outputPath: `${baseDir}/en-US`, translation: { helloPartial: 'Hello', hello: 'Hello i18n!', plural: 'Updated 3 minutes ago', date: 'January', }, }, { lang: 'fr', outputPath: `${baseDir}/fr`, translation: { helloPartial: 'Bonjour', hello: 'Bonjour i18n!', plural: 'Mis à jour il y a 3 minutes', date: 'janvier', }, translationReplacements: [ ['Hello', 'Bonjour'], ['Updated', 'Mis à jour'], ['just now', 'juste maintenant'], ['one minute ago', 'il y a une minute'], [/other {/g, 'other {il y a '], ['minutes ago', 'minutes'], ], }, { lang: 'de', outputPath: `${baseDir}/de`, translation: { helloPartial: 'Hallo', hello: 'Hallo i18n!', plural: 'Aktualisiert vor 3 Minuten', date: 'Januar', }, translationReplacements: [ ['Hello', 'Hallo'], ['Updated', 'Aktualisiert'], ['just now', 'gerade jetzt'], ['one minute ago', 'vor einer Minute'], [/other {/g, 'other {vor '], ['minutes ago', 'Minuten'], ], }, ]; export const sourceLocale = langTranslations[0].lang; export async function browserCheck(page: Page, lang: string) { const translation = langTranslations.find((t) => t.lang === lang)?.translation; if (!translation) { throw new Error(`Could not find translation for language '${lang}'`); } const getParagraph = async (id: string) => page.$eval(`p#${id}`, (el) => el.textContent?.trim()); const hello = await getParagraph('hello'); if (hello !== translation.hello) { throw new Error(`Expected 'hello' to be '${translation.hello}', but got '${hello}'.`); } const locale = await getParagraph('locale'); if (locale !== lang) { throw new Error(`Expected 'locale' to be '${lang}', but got '${locale}'.`); } const date = await getParagraph('date'); if (date !== translation.date) { throw new Error(`Expected 'date' to be '${translation.date}', but got '${date}'.`); } const plural = await getParagraph('plural'); if (plural !== translation.plural) { throw new Error(`Expected 'plural' to be '${translation.plural}', but got '${plural}'.`); } } export interface ExternalServer { readonly server: Server; readonly port: number; readonly url: string; } /** * Create an `express` `http.Server` listening on a random port. * * Call .close() on the server return value to close the server. */ export async function externalServer(outputPath: string, baseUrl = '/'): Promise { const app = express(); app.use(baseUrl, express.static(resolve(outputPath))); return new Promise((resolve) => { const server = app.listen(0, 'localhost', () => { const { port } = server.address() as AddressInfo; resolve({ server, port, url: `http://localhost:${port}${baseUrl}`, }); }); }); } export const baseHrefs: { [l: string]: string } = { 'en-US': '/en/', fr: '/fr-FR/', de: '', }; export async function setupI18nConfig() { // Add component with i18n content, both translations and localeData (plural, dates). await writeFile( 'src/app/app.ts', ` import { Component, Inject, LOCALE_ID } from '@angular/core'; import { DatePipe } from '@angular/common'; import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', imports: [DatePipe, RouterOutlet], templateUrl: './app.html' }) export class App { constructor(@Inject(LOCALE_ID) public locale: string) { } title = 'i18n'; jan = new Date(2000, 0, 1); minutes = 3; } `, ); await writeFile( `src/app/app.html`, `

Hello {{ title }}!

{{ locale }}

{{ jan | date : 'LLLL' }}

Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}

`, ); await createDir(dirname(translationFile)); await writeFile( translationFile, ` Hello ! src/app/app.html 2,3 An introduction header for this sample Updated src/app/app.html 5,6 {VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other { minutes ago}} src/app/app.html 5,6 `, ); // Add a dynamic import to ensure syntax is supported // ng serve support: https://github.com/angular/angular-cli/issues/16248 await writeFile('src/app/dynamic.ts', `export const abc = 5;`); await appendToFile( 'src/app/app.ts', ` (async () => { await import('./dynamic'); })(); `, ); // Update angular.json to build, serve, and test each locale. await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; const appArchitect = workspaceJson.projects['test-project'].architect; const buildConfigs = appArchitect['build'].configurations; const serveConfigs = appArchitect['serve'].configurations; appArchitect['build'].defaultConfiguration = undefined; // Always error on missing translations. appArchitect['build'].options.optimization = true; appArchitect['build'].options.aot = true; appArchitect['build'].options.i18nMissingTranslation = 'error'; appArchitect['build'].options.sourceMap = true; appArchitect['build'].options.outputHashing = 'none'; const useWebpackBuilder = !getGlobalVariable('argv')['esbuild']; if (useWebpackBuilder) { appArchitect['build'].options.buildOptimizer = true; appArchitect['build'].options.vendorChunk = true; } // Enable localization for all locales appArchitect['build'].options.localize = true; // Add i18n config items (app, build, serve, e2e). // tslint:disable-next-line: no-any const i18n: Record = (appProject.i18n = { locales: {} }); for (const { lang } of langTranslations) { if (lang === sourceLocale) { i18n.sourceLocale = lang; } else { i18n.locales[lang] = `src/locale/messages.${lang}.xlf`; } buildConfigs[lang] = { localize: [lang] }; serveConfigs[lang] = { buildTarget: `test-project:build:${lang}` }; } }); // Install the localize package if using ivy let localizeVersion = '@angular/localize@' + readNgVersion(); if (getGlobalVariable('argv')['ng-snapshots']) { localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize']; } await installPackage(localizeVersion); // Make translations for each language. for (const { lang, translationReplacements } of langTranslations) { if (lang != sourceLocale) { await copyFile(translationFile, `src/locale/messages.${lang}.xlf`); for (const replacements of translationReplacements!) { await replaceInFile( `src/locale/messages.${lang}.xlf`, new RegExp(replacements[0], 'g'), replacements[1] as string, ); } for (const replacement of [[/source/g, 'target']]) { await replaceInFile( `src/locale/messages.${lang}.xlf`, new RegExp(replacement[0], 'g'), replacement[1] as string, ); } } } }