transloco
transloco copied to clipboard
Bug(inline-loader): Translations with the inline loader strategy only work at the template level
Is there an existing issue for this?
- [x] I have searched the existing issues
Which Transloco package(s) are the source of the bug?
Transloco, Locale
Is this a regression?
Yes
Current behavior
I try to get translations through my component's service, but it always throws error that the translation has not been found, but in the component's template it works perfectly.
I have tried the .translate() and .selectTranslate() methods and nothing works.
Code
@Component({
templateUrl: './commands.component.html',
styleUrls: ['./commands.component.less'],
providers: [
{
provide: TRANSLOCO_SCOPE,
multi: true,
useValue: {
scope: 'commands/page',
alias: 'commands',
loader: i18nLoader((lang: string) => import(`./i18n/${lang}.json`)),
},
},
],
})
export class CommandsComponent {
constructor(private i18nService: TranslocoService) {
this.i18nService.selectTranslate('commands.service.meta.title').subscribe((x) => console.log(x));
}
}
Results



Expected behavior
Correctly obtain the translations at the service and template level of the component.
Please provide a link to a minimal reproduction of the bug
N/A
Transloco Config
No response
Please provide the environment you discovered this bug in
Transloco: 3.1.1
Angular: 13.1.1
Node: 16.11.0
Package Manager: yarn
OS: Windows 11
Browser
Microsoft Edge Version 97.0.1072.55
Additional context
The translation keys are correct and I have also tried to use the same translation keys that are in the template and they work but it does not work at the service level either.
I would like to make a pull request for this bug
No
Hi, @kaname-png I would like to work upon this issue if no one else has picked it up already.
@noobyogi0010 please.
Can you take a look at the problem? @shaharkazaz
@noobyogi0010 do you still want this issue? :) @kaname-png I'm swamped I couldn't reach the issue or any other in matter of fact that are pending in Transloco. I'll try to get to it sometime soon.
Thanks for answering @shaharkazaz, I hope you can or someone can solve this problem, this prevents me from being able to continue with my project since I need it.
And thank you very much in advance.
Hey @shaharkazaz any update on this issue? I am facing the same issue.
@mehrad-rafigh you are welcome to pick this up and open a PR :) I have no estimate on this from my end.
@shaharkazaz I would like to do that. Can you please point me to the direction? I haven't contributed to transloco before
@mehrad-rafigh I'm currently on vacation so I don't have my laptop and I didn't get the chance to investigate the issue myself.
I suggest you start by creating a small dedicated repo that reproduces the issue and try to work your way from there by using a local version of the library so you can debug the issue easily.
I encountered the same issue, but I found that if you pass a scope as the third argument to the selectTranslate method, it will wait for the scope to load. You can't pass in a string like scopeName, but if you pass in a ProviderScope (easiest thing to do is probably just to inject the TRANSLOCO_SCOPE object), it works.
To cater to your specific example, I think the following will work:
this.i18nService.selectTranslate('service.meta.title', {}, this.translocoScope[0]).subscribe((x) => console.log(x));
I used an array accessor there since you have the scope provider marked as multi: true, so to be fair, it might not be at the start of the array, but I think you get the point. this.translcocoScope would be the injected token btw.
Also facing the same issue here :)
The issue arises when scopes are provided via mutli: true.
It seems like the scopes are not yet necessarily fully loaded when using translocoService.translate(...). translate only looks the key up in the existing translations. When this method is being called before the scope was loaded before, it returns undefined.
@gmiklich approach works fine and it does make sense that a asynchronous method is needed when retrieving a lazy module translation.
I thought about a way by initialize lazy loaded modules during the dynamic import, so that the localization download is being included in the lazy load but I didn't come up this a good solution. Maybe some of you guys has an idea
I wrote this provider factory, that might be useful for one or another person.
import { Provider } from '@angular/core';
import { TRANSLOCO_SCOPE } from '@ngneat/transloco';
export function translateProvider(scope : string) : Provider
{
const provider : Provider =
{
provide: TRANSLOCO_SCOPE,
useValue:
{
scope,
loader:
{
en: () => import('libs/frontend/' + scope + '/src/assets/i18n/en.json'),
de: () => import('libs/frontend/' + scope + '/src/assets/i18n/de.json')
}
},
multi: true
};
return provider;
}
It can either be used in modules (feature lib) or directly in components:
providers:
[
translateProvider('shared')
]
Unfortunately I face the same issue of unloaded scopes which are causing flickers and repaints on the app. From my observation using translate() in pipes is totally broken - only using pure: false works as a workaround otherwise the untranslated text stays on first load, on second load it appears instantly. My app is using a typical app-shell - feature-one - shared approach, where loading the shared scope and therefore the second is causing issues.
I don't know if it helps, but I found kind of a hackaround.
First of all, my setup: Host app module defines a http loader
@NgModule({
exports: [ TranslocoModule ],
providers: [
{
provide: TRANSLOCO_CONFIG,
useValue: translocoConfig({
availableLangs: ['en', 'de'],
defaultLang: 'en',
// Remove this option if your application
// doesn't support changing language in runtime.
reRenderOnLangChange: true,
prodMode: !isDevMode(),
})
},
{ provide: TRANSLOCO_LOADER, useClass: TranslocoHttpLoader }
]
})
export class TranslocoRootModule {}
@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
private readonly http = inject(HttpClient)
getTranslation(lang: string) {
return this.http.get<Translation>(`/assets/i18n/${lang}.json`);
}
}
The feature module with the scope is imported in the root app module and provides the scope like so:
{
provide: TRANSLOCO_SCOPE,
multi: true,
useValue: {
scope: 'feature-scope-name',
loader: scopeLoader(
(lang: string, root: string) => import(`./${root}/${lang}.json`)
),
},
},
Using translations from the scope feature-scope-name sometimes works, sometimes not. I didn't find out, why sometimes the scope fails and why sometimes it doesn't.
Solution
I now decided not to use a scopeLoader for this use case and instead, trying to utilize the httpLoader from the root module for this job. I removed the loader from feature.module.ts:
{
provide: TRANSLOCO_SCOPE,
multi: true,
useValue: {
scope: 'feature-scope-name',
},
},
Additional I wrote an APP_INITIALIZER factory app.module.ts
{
provide: APP_INITIALIZER,
useFactory: initialize,
multi: true,
deps: [TranslocoService],
},
export function initialize( translateService: TranslocoService ): () => Promise<void> {
return () =>
new Promise<void>(async (resolve) => {
// Setting default language to enforce loading translation before app starts
translateService.setActiveLang(translateService.getDefaultLang());
// Scope hack...
const scopeLang = translateService._completeScopeWithLang('inspector');
await firstValueFrom(translateService.selectTranslation(scopeLang));
resolve();
});
}
Additionally you have to output the library i18n files into the app assets in angular.json(or project.json in NX):
"assets": [
{
"input": "libs/feature/src/lib/i18n",
"glob": "**/*",
"output": "assets/i18n/feature-scope-name"
}
],
Conclusion
I think, it's not a good solution because you need extra steps like defining the assets in the angular.json. Additionally the languages initialize on app start which also is not good from performance side. But since scopes are somehow broken, this is my way to go for now.
Lazy Loading
For lazy loaded modules, this approach also should work. But yeah, it's a fail to load the child modules translations before the actual child module has been loaded. Maybe the scope hack can be executed right before the lazy module is being imported. Maybe in a guard?