Skip to content

Commit 10bef5c

Browse files
committed
refact: use appConfig file for translations and adapt server.ts
- add option to use appConfig file for translations - adapt server.ts to use appConfig file for translations - adapt language.service to use appConfig file for translations and redirect to browser language in case it is in supported locales config file Reviewed-by: acapai
1 parent 47659a6 commit 10bef5c

8 files changed

Lines changed: 215 additions & 455 deletions

File tree

frontend/package-lock.json

Lines changed: 92 additions & 342 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
"build:i18n-ssr": "npm run build:client-and-server-bundles-i18n && npm run compile:server",
2121
"build:client-and-server-bundles": "ng build --prod && ng run frontend:server:production",
2222
"build:client-and-server-bundles-i18n": "ng run frontend:build:fr && ng run frontend:build:en && ng run frontend:build:de && ng run frontend:server:production",
23-
"extract-i18n": "ng xi18n frontend --i18n-format xlf --output-path i18n --i18n-locale fr && ng run frontend:xliffmerge",
24-
"start:proxy": "ts-node tools/dev-i18n-proxy.ts",
25-
"start:all": "concurrently -k \"ng serve -c fr --port 4200\" \"ng serve -c en --port 4201\" \"ng serve -c de --port 4202\" \"npm run start:proxy\""
23+
"extract-i18n": "ng xi18n frontend --i18n-format xlf --output-path i18n --i18n-locale fr && ng run frontend:xliffmerge"
2624
},
2725
"private": true,
2826
"dependencies": {
@@ -71,7 +69,6 @@
7169
"@angular/compiler-cli": "^8.2.14",
7270
"@angular/language-service": "^8.2.14",
7371
"@ngx-i18nsupport/ngx-i18nsupport": "^1.1.6",
74-
"@types/express": "^5.0.3",
7572
"@types/jasmine": "^3.9.1",
7673
"@types/jasminewd2": "^2.0.10",
7774
"@types/leaflet": "^1.7.5",
@@ -81,11 +78,9 @@
8178
"@typescript-eslint/eslint-plugin": "^4.33.0",
8279
"@typescript-eslint/parser": "^4.33.0",
8380
"codelyzer": "^5.2.2",
84-
"concurrently": "^6.4.0",
8581
"eslint": "^7.32.0",
8682
"eslint-config-prettier": "^7.2.0",
8783
"eslint-plugin-prettier": "^3.4.1",
88-
"http-proxy-middleware": "^3.0.5",
8984
"jasmine-core": "~3.4.0",
9085
"jasmine-spec-reporter": "~4.2.1",
9186
"karma": "~4.2.0",

frontend/server.ts

Lines changed: 76 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
77

88
import * as express from 'express';
99
import { join } from 'path';
10-
import { readFileSync } from 'fs';
10+
import { readFileSync, readdirSync, statSync } from 'fs';
11+
import { AppConfig } from './src/conf/app.config';
1112

12-
// Faster server renders w/ Prod mode (dev mode never needed)
13+
// Faster server renders w/ Prod mode
1314
enableProdMode();
1415

1516
// Express server
@@ -18,84 +19,106 @@ const app = express();
1819
const PORT = process.env.PORT || 4000;
1920
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
2021

21-
const supportedLocales = ['de','en', 'fr'];
22-
const DEFAULT_LOCALE = 'fr';
22+
// Liste de toutes les locales possibles
23+
const ALL_LOCALES = AppConfig.supportedLocales || ["fr", "en", "de"];
24+
const DEFAULT_LOCALE = AppConfig.defaultLocale || 'fr';
25+
26+
// Détecte quels sous-dossiers existe réellement sous dist/browser
27+
const actualFolders = readdirSync(DIST_FOLDER).filter(name => {
28+
const p = join(DIST_FOLDER, name);
29+
return statSync(p).isDirectory();
30+
});
31+
32+
// Ne retenir que ceux qui sont dans ALL_LOCALES
33+
let supportedLocales = actualFolders.filter(loc => ALL_LOCALES.includes(loc));
34+
// Si aucun sous-dossier, on est en mono-locale
35+
if (supportedLocales.length === 0) {
36+
supportedLocales = [DEFAULT_LOCALE];
37+
}
38+
// Vérification config vs build
39+
if (ALL_LOCALES.length > 1 && supportedLocales.length < ALL_LOCALES.length) {
40+
console.error('Build error: expected locale folders ' + ALL_LOCALES.join(', ') + ' but found ' + supportedLocales.join(', ') + '. You may need to run `npm run build:i18n-ssr in case of multi-locale or change `supportedLocales` in app.config.ts to deploy mono locale.');
41+
process.exit(1);
42+
}
43+
const isMulti = supportedLocales.length > 1;
44+
45+
// Charger et mocker window/document pour SSR
2346
const MockBrowser = require('mock-browser').mocks.MockBrowser;
2447
const mock = new MockBrowser();
2548
const domino = require('domino');
26-
const template = readFileSync(
27-
join(DIST_FOLDER, DEFAULT_LOCALE, 'index.html')
28-
).toString();
49+
50+
// Sélection du template index.html
51+
const templatePath = isMulti
52+
? join(DIST_FOLDER, DEFAULT_LOCALE, 'index.html')
53+
: join(DIST_FOLDER, 'index.html');
54+
const template = readFileSync(templatePath).toString();
55+
2956
const win = domino.createWindow(template);
30-
win.Object = Object;
31-
win.Math = Math;
32-
win.screen = { deviceXDPI: 1 };
57+
Object.assign(win, {
58+
Object,
59+
Math,
60+
screen: { deviceXDPI: 1 }
61+
});
3362
global['window'] = win;
3463
global['document'] = win.document;
3564
global['navigator'] = mock.getNavigator();
36-
global['branch'] = null;
37-
global['object'] = win.object;
65+
global['localStorage'] = mock.getLocalStorage();
3866
global['HTMLElement'] = win.HTMLElement;
3967
global['HTMLAnchorElement'] = win.HTMLAnchorElement;
4068
global['DOMTokenList'] = win.DOMTokenList;
4169
global['Node'] = win.Node;
4270
global['Text'] = win.Text;
43-
global['localStorage'] = win.localStorage = mock.getLocalStorage();
4471
global['L'] = require('leaflet');
4572

46-
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
73+
// Bundle server-side (Webpack)
4774
const {
48-
AppServerModuleNgFactory,
49-
LAZY_MODULE_MAP,
75+
AppServerModuleNgFactory,
76+
LAZY_MODULE_MAP,
5077
} = require('./dist/server/main');
5178

52-
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
53-
app.engine(
54-
'html',
55-
ngExpressEngine({
56-
bootstrap: AppServerModuleNgFactory,
57-
providers: [provideModuleMap(LAZY_MODULE_MAP)],
58-
})
59-
);
60-
79+
// Setup Express engine
80+
app.engine('html', ngExpressEngine({
81+
bootstrap: AppServerModuleNgFactory,
82+
providers: [provideModuleMap(LAZY_MODULE_MAP)]
83+
}));
6184
app.set('view engine', 'html');
6285
app.set('views', DIST_FOLDER);
6386

64-
// Example Express Rest API endpoints
65-
// app.get('/api/**', (req, res) => { });
66-
// Serve static files from /browser
67-
app.get(
68-
'*.*',
69-
express.static(DIST_FOLDER, {
70-
maxAge: '1y',
71-
})
72-
);
87+
// Serve static assets
88+
app.get('*.*', express.static(DIST_FOLDER, { maxAge: '1y' }));
7389

7490
// All regular routes use the Universal engine
7591
app.get('*', (req, res) => {
92+
if (isMulti) {
93+
// Multi-locale: URL comme /fr/chemin
7694
const matches = req.url.match(/^\/([a-z]{2}(?:-[A-Z]{2})?)\//);
77-
// check if the requested url has a correct format '/locale' and matches any of the supportedLocales
78-
const locale =
79-
matches && supportedLocales.indexOf(matches[1]) !== -1
80-
? matches[1]
81-
: DEFAULT_LOCALE;
82-
83-
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
84-
if (ip.substr(0, 7) === '::ffff:') {
85-
ip = ip.substr(7);
86-
}
87-
// res.render("index", { req });
88-
res.render(`${locale}/index`, {
89-
req: req,
90-
url: req.url.replace(`/${locale}/`, '/'),
91-
providers: [
92-
{ provide: 'language', useFactory: () => locale, deps: [] },
93-
{ provide: 'ip', useFactory: () => ip, deps: [] },
94-
],
95+
const locale = (matches && supportedLocales.includes(matches[1]))
96+
? matches[1]
97+
: DEFAULT_LOCALE;
98+
99+
// Ajuste URL interne pour Angular
100+
const internalUrl = req.url.replace(`/${locale}/`, '/');
101+
102+
return res.render(`${locale}/index`, {
103+
req,
104+
url: internalUrl,
105+
providers: [
106+
{ provide: 'language', useFactory: () => locale, deps: [] }
107+
]
95108
});
109+
}
110+
111+
// Mono-locale: toujours servir index.html à la racine
112+
return res.render('index', {
113+
req,
114+
url: req.url,
115+
providers: [
116+
{ provide: 'language', useFactory: () => DEFAULT_LOCALE, deps: [] }
117+
]
118+
});
96119
});
97120

98121
// Start up the Node server
99122
app.listen(PORT, () => {
100-
console.log(`Node Express server listening on http://localhost:${PORT}`);
123+
console.log(`Node Express server listening on http://localhost:${PORT}`);
101124
});

frontend/src/app/app.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { MainConfig } from '../conf/main.config';
1111
import { Router, NavigationStart } from '@angular/router';
1212
import { ModalsTopbarService } from './core/topbar/modalTopbar.service';
1313
import { TaxhubService } from './api/taxhub.service';
14+
import { LanguageService } from './core/services/language.service';
1415

1516
@Component({
1617
selector: 'app-root',
@@ -33,6 +34,7 @@ export class AppComponent implements OnInit {
3334
private titleService: Title,
3435
private modalService: ModalsTopbarService,
3536
private _taxhubService: TaxhubService,
37+
private languageService: LanguageService
3638
) {
3739
this.router.events.subscribe((event) => {
3840
if (event instanceof NavigationStart) {
@@ -46,6 +48,7 @@ export class AppComponent implements OnInit {
4648
}
4749

4850
ngOnInit() {
51+
this.languageService.init();
4952
this.MainConfig = MainConfig;
5053
this.backgroundImage =
5154
MainConfig.API_ENDPOINT + '/media/background.jpg';

frontend/src/app/core/services/language.service.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,49 @@
1-
import { Injectable } from '@angular/core';
2-
1+
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
2+
import { isPlatformBrowser } from '@angular/common';
3+
import { MainConfig } from '../../../conf/main.config';
34
@Injectable({
45
providedIn: 'root'
56
})
67
export class LanguageService {
78

8-
// Supported locales, adjust as needed
9-
private supportedLocales = ['fr', 'en', 'de'];
10-
private defaultLocale = 'fr';
9+
private supportedLocales = MainConfig.supportedLocales;
10+
private defaultLocale = MainConfig.defaultLocale;
11+
12+
constructor(
13+
@Inject(PLATFORM_ID) private platformId: Object
14+
) {}
15+
16+
/**
17+
* À appeler au bootstrap de l'app pour :
18+
* - si aucune locale dans l'URL, rediriger vers la locale
19+
* du navigateur (si supportée) ou la locale par défaut.
20+
*/
21+
init(): void {
22+
if (!isPlatformBrowser(this.platformId)) {
23+
// ne rien faire côté serveur
24+
return;
25+
}
26+
// Si mono-locale, on n’a rien à faire (pas de switcher ni de redirection)
27+
if (this.supportedLocales.length < 2) {
28+
return;
29+
}
30+
const parts = window.location.pathname.split('/');
31+
const maybeLocale = parts[1];
1132

33+
// si l'URL comporte déjà une locale valide : OK
34+
if (this.supportedLocales.includes(maybeLocale)) {
35+
return;
36+
}
37+
38+
// sinon, on récupère la langue du navigateur
39+
const browserLang = navigator.language.split('-')[0];
40+
const target = this.supportedLocales.includes(browserLang)
41+
? browserLang
42+
: this.defaultLocale;
43+
44+
// on redirige vers /<target>/...
45+
this.switchLanguage(target);
46+
}
1247

1348
getCurrentLocale(): string {
1449
const seg = window.location.pathname.split('/')[1];

frontend/src/app/core/topbar/topbar.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
</a>
7777
</ng-template>
7878
</div>
79-
<li class="nav-item dropdown" ngbDropdown [autoClose]="'outside'">
79+
<li class="nav-item dropdown" ngbDropdown [autoClose]="'outside'" *ngIf="MainConfig.supportedLocales.length > 1">
8080
<!-- switcher Language-->
8181
<a class="nav-link d-flex align-items-center dropdown-toggle p-0 pl-5"
8282
id="langDropdown"

frontend/src/conf/app.config.ts.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export const AppConfig = {
33
API_ENDPOINT: 'http://localhost:5002/api',
44
API_TAXHUB: 'http://localhost:5000/api',
55
API_CITY: 'https://nominatim.openstreetmap.org/reverse',
6+
defaultLocale: 'fr', // langue de repli
7+
supportedLocales: ['fr','en','de'], // langues actives
68
// HCAPTCHA_SITE_KEY: null,
79
// FRONTEND: {
810
// PROD_MOD: true,

frontend/tools/dev-i18n-proxy.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

0 commit comments

Comments
 (0)