I am implementing an i18n for a personal project which uses Atomic Design for components. So far I haven't encountered any problems (and I am pretty new to Angular), but for the last couple of days I've lost sleep on this topic. Trying to extract the i18n properties has proven to be more troublesome than it's been worth it so far.
THE PROBLEM
Running ng extract-i18n
only works for app.component.html
and not for the subfolders.
I've tried this also in a barebone project:
ng new testProject
cd testProject
npm i @angular/localize
ng generate component testA
ng generate component components/testB
Now for a bit of grunt work:
Modify each component to include a simple i18n declaration. I used a these in respective files:
<p i18n="@@app">app</p> <!--@ src/app/app.component.html-->
<p i18n="@@testA">test-a works!</p> <!--@ src/app/test-a/test-a.component.html-->
<p i18n="@@testB">test-b works!</p> <!-- src/app/components/test-b.component.html-->
Finally, run ng extract-i18n --outputPath="src/i18n"
and you should see src/i18n/messages.json
. However, if you notice bellow, only @@app
is being localized, and the rest aren't.
{
"locale": "en-US",
"translations": {
"app": "app"
}
}
HOWEVER
During my search for a solution to this problem, I found this demo project. As you can notice, it also has an Atomic Design structure (or at least a version of it).
git clone https://github.com/PhraseApp-Blog/angular-i18n-2022
cd angular-i18n-2022
npm install
ng extract-i18n --outputPath="src/locale" --format="json"
The only modified file is the nested src/app/layout/navbar/navbar.component.html
to test my theory, everything else comes from the demo project.
The messages.json
output:
{
"locale": "en-CA",
"translations": {
"1475737827206181859": "Demo companion app for\n <a href=\"{$PH}\">Phrase blog</a>\n post.\n <a href=\"{$PH_1}\">Made with Angular</a>.",
"navbar": "navbar",
"615967567956853879": "azcadea logo",
"7285274704628585281": "azcadea",
"2821179408673282599": "Home",
"1726363342938046830": "About",
"8856905278208146821": " {$ICU} ",
"7185053985224017078": "{VAR_PLURAL, plural, =0 {Sold out} =1 {Available at 1 outlet} other {Available at\n {INTERPOLATION} outlets}}",
"4481891408571585417": " Add to cart ",
"6223458566712133696": "Image of {$PH}"
}
}
SUMMARY
I tried comparing my config files to the demo project (angular.json, package.json, tsconfig.app.json, tsconfig.json, tsconfig.spec.json
). I've gone over any possible documentation I could find, back and forth. The only notable differences I could find were the angular version latest vs 13.3.0
and the module version in tsconfig.json
latest vs es2017/es2022
.
My hunch says it's something very simple and I'm just missing a property/path or some declaration to scan the subfolders and not just the app folder. But I've found no indication how to enable this.
Any advice or feedback would be appreciated.
UPDATE
As Volodymyr Mishyn's comment suggests, the component needs to be referenced in other components/modules, for the i18n to be extracted
Therefore:
The example I give at the top, creating new components, doesn't work as the newly generated components don't get automatically referenced anywhere, until you import them in the respective module.
What I missed:
In the demo project, when you generate a component, it automatically gets added to app.module.ts
and therefore instantly can be i18n-ed.
In my project, this behavior is not default, so the new component is detached and therefore not i18n-ed - as intended. This may be due to different Angular versions, but that's just speculation on my part.
What confuses me:
I added i18n to my project in the header component src/app/components/header/header.component.html
.
Obviously it was referenced and built, yet until now the extraction wasn't working. The best I can describe this behavior is that the extraction pipeline was somehow blocked, but it's a weak explanation that holds little merit.
Are your components used somewhere? like in other components or routing.
I got the theory, that the problem is related to tree-shaking.
your components don't finish up in the final bundle during build, and so no i18n got extracted.
I tried this theory on my pet project
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { FirstComponent } from './first/first.component';
// import { SecondComponent } from './second/second.component'; Commented this line
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'first', component: FirstComponent },
// { path: 'second', component: SecondComponent }, and this line
{
path: '**',
redirectTo: '/home',
},
];
and running
ng extract-i18n --output-path src/locale
omits i18n strings from SecondComponent in result file
So try using your components somewhere.