I was using this library ngx-translate-multi-http-loader to being able to load multiple language file for my ionic 5 App.
I didn't had any errors, but the translation wasn't working.
Then I added inside the TranslateModule.forRoot({})
the defaultLanguage: 'en'
, and it then resulted in the following error message
Something went wrong for the following translation file: ./assets/i18n/en.json
Database not created. Must call create() first
I needed a lot of time to figure it out how to fix it and couldn't find no help on internet.
This error shouldn't happen anymore with >= 9.2.0. Since the library is now using httpBackend
This topic pointed me in the right direction. Basically, the required loader that we use to import the translations files
export function HttpLoaderFactory(httpClient: HttpClient) {
return new MultiTranslateHttpLoader(httpClient, [
{ prefix: './assets/i18n/', suffix: '.json' },
{ prefix: `./assets/i18n/${APP_CONFIG.appType}/`, suffix: '.json' },
])
}
Is directly imported into the app.module.ts @ngModule
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
If you find this post, it surely mean you're using an HttpInterceptor
in your app, that does request the data from the ionic storage (or any other services) to apply a specific logic to the request -> Let say, you want to add a token to the request.
Let's have the following example
@Injectable()
export class AuthHttpInterceptor implements HttpInterceptor {
constructor(private _storage: Storage) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
// Clone the request to add the new header.
let authReq: any
return from(this.getToken()).pipe(
switchMap((token) => {
if (!req.headers.get('Authorization'))
authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`),
})
else authReq = req.clone()
//send the newly created request
return next.handle(authReq).pipe(
retry(1),
catchError((err, caught) => {
const error = (err && err.error && err.error.message) || err.statusText
return throwError(error)
}) as any,
)
}),
)
}
getToken(): Observable<any> {
const token$ = new Observable((observer) => {
this._storage.get('token').then(async (token) => {
if (token) {
observer.next(token)
} else {
observer.next(ORGANISATION_TOKEN)
}
})
})
return token$
}
}
Then, because the ngx-translate-multi-http-loader
is requesting the translations file with the angular default http
class, you'll be passing through this http interceptor. But the _storage
isn't instantiated yet. which result in an Database not created. Must call create() first
which make our request fail into the Something went wrong for the following translation file: ./assets/i18n/en.json
We have to ignore this interceptor for that specific request.
For that purpose :
you could do it with a httpBackend
-> here an explanation.
But it wont work, because you wont have instantiate your services before the http call.
You have to add a specific header, that tell your HttpInterceptor
to ignore the request and let it going through without further investigation. Thx JohnrSharpe
export const InterceptorSkipHeader = 'X-Skip-Interceptor'
@Injectable()
export class UsHttpInterceptor implements HttpInterceptor {
constructor(private _storage: Storage, private _statusService: MegaphoneStatusService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
if (req.headers.has(InterceptorSkipHeader)) {
const headers = req.headers.delete(InterceptorSkipHeader)
return next.handle(req.clone({ headers }))
}
//... Intercept
For being able to pass that header into the ngx-translate-multi-http-loader
, well.. you can't. because the library do not accept it, but it isn't complicated to copy it.
// core.ngx-loader.ts
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { TranslateLoader } from '@ngx-translate/core'
import merge from 'deepmerge'
import { forkJoin, Observable, of } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { InterceptorSkipHeader } from '../http.interceptor'
export class MultiTranslateHttpLoader implements TranslateLoader {
constructor(
private http: HttpClient,
private resources: {
prefix: string
suffix: string
}[],
) {}
public getTranslation(lang: string): Observable<any> {
const headers = new HttpHeaders().set(InterceptorSkipHeader, '') // <-- Our Skip interceptor
const requests = this.resources.map((resource) => {
const path = resource.prefix + lang + resource.suffix
return this.http.get(path, { headers }).pipe( // <-- We add the header into the request
catchError((res) => {
console.error('Something went wrong for the following translation file:', path)
console.error(res.message)
return of({})
}),
)
})
return forkJoin(requests).pipe(map((response) => merge.all(response)))
}
}
And there you are.