Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { TranslateService } from '@ngx-translate/core';

import { appConfig } from './app.config';

/**
* Guards the NG0200 regression introduced by the ngx-translate v18 upgrade.
*
* v18 loads `fallbackLang` eagerly from inside the TranslateService constructor. Setting it in
* `provideTranslateService()` therefore resolves TranslateLoader while the injector is still
* building TranslateService, which fails with NG0200 (circular dependency) and leaves the
* fallback language — English — rendering raw keys, while every other locale loaded fine.
*
* These tests exercise the real `appConfig` providers, so re-adding `fallbackLang` there fails
* here rather than silently in production.
*/
describe('appConfig translation wiring', () => {
beforeEach(() => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [...appConfig.providers, provideHttpClientTesting()],
});
});

it('constructs TranslateService without a circular dependency', () => {
expect(() => TestBed.inject(TranslateService)).not.toThrow();
});

it('does not fetch any translation before a language is requested', () => {
TestBed.inject(TranslateService);
// An eager fallback load would already have issued a request here.
TestBed.inject(HttpTestingController).verify();
});

it('loads English through the configured HTTP loader once requested', () => {
const translate = TestBed.inject(TranslateService);
const http = TestBed.inject(HttpTestingController);

translate.use('en');
http.expectOne('./assets/i18n/en.json').flush({ NAV: { DASHBOARD: 'Dashboard' } });

expect(translate.instant('NAV.DASHBOARD')).toBe('Dashboard');
http.verify();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ export const appConfig: ApplicationConfig = {
// refresh + retry) before errorInterceptor redirects to the login page.
provideHttpClient(withInterceptors([authInterceptor, errorInterceptor, oidcRefreshInterceptor])),
provideAnimationsAsync(),
// No `fallbackLang` here on purpose. ngx-translate v18 loads the fallback language eagerly
// from inside the TranslateService constructor, which resolves TranslateLoader while the
// injector is still building TranslateService -- a circular dependency that fails with
// NG0200 ("error loading translation for en"). Only the fallback language is affected, so
// English silently rendered raw keys while every other locale, loaded later via use(),
// worked fine. v17's `defaultLanguage` did not load eagerly, which is why this only
// appeared after the v18 upgrade.
//
// I18nService.init() calls setFallbackLang('en') instead, after bootstrap, where the
// injector is complete and the loader resolves normally.
provideTranslateService({
fallbackLang: 'en',
loader: {
provide: TranslateLoader,
useClass: TranslateHttpLoader,
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- **English UI showed raw translation keys (`NAV.DASHBOARD`, `AUTH.SIGN_IN`) after upgrading to v2.12.0.** The ngx-translate v18 upgrade moved `defaultLanguage` to `fallbackLang`, but v18 loads the fallback language *eagerly from inside the `TranslateService` constructor*. Setting it in `provideTranslateService()` therefore resolved `TranslateLoader` while the injector was still building `TranslateService`, failing with `NG0200: Circular dependency detected`. Only the fallback language was affected, so English rendered raw keys while every other locale — loaded later via `use()` — worked normally. The fallback is now set in `I18nService.init()` after bootstrap, where the injector is complete. Regression tests exercise the real `appConfig` providers, so re-adding `fallbackLang` to the provider config fails CI.

## [2.12.0] - 2026-08-05

### Added
Expand Down
Loading