```
Remember to import the Pipe into the Component:
```typescript
@Component({
// ...
imports: [I18NextPipe],
})
export class SomeExampleComponent {}
```
Trigger native i18next [format method](https://www.i18next.com/formatting.html) by using I18NextFormatPipe or I18NextPipe with option 'format':
`{{ 'any_key' | i18next | i18nextFormat }}`
`{{ 'any_key' | i18next: { format: 'cap' } }}`
`{{ 'any_key' | i18nextCap }}`
**Note:** Using "i18nextCap" you will get the same result as `i18next: { format: 'cap' }`
**REMEMBER** that format will not work until you set "interpolation.format" function in i18next options.
`angular-i81next` has static method `static interpolationFormat(customFormat: Function = null): Function` that can be used as default interpolation format function (it provides 'upper', 'cap' and 'lower' formatters). You also can pass your custom function to be called after library formatters:
```typescript
import { defaultInterpolationFormat, interpolationFormat } from "angular-i18next";
const i18nextOptions = {
supportedLngs: ['en', 'ru'],
ns: [
'translation',
'validation',
'error',
],
interpolation: {
format: interpolationFormat((value, format, lng) => {
// extend interpolation format
if(value instanceof Date)
return moment(value).format(format);
return value;
});
// format: interpolationFormat(defaultInterpolationFormat)
}
};
```
**i18nextEager pipe**
This is the impure analog of *i18next pipe* that is subscribed to language change, it will change string right away to choosen language (without reloading page).
**Warning!**: Use i18nextEager only in combine with [OnPush change detection strategy](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4), or else (default change detection) each pipe will retrigger more than one time (cause of performance issues).
Subscribing to event observables:
```typescript
this.i18NextService.events.languageChanged.subscribe(lang => {
// do something
})
```
Add a provider to module/component if you want to prefix child i18next keys:
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: 'feature' // set 'feature:' prefix
}
```
```typescript
{
provide: I18NEXT_SCOPE,
useValue: 'person' // set 'person.' prefix
}
```
Since v3.1.0+ it is possible to pass array of namespaces (or scopes). [Key would fallback](https://www.i18next.com/api.html#t) to next namespace in array if the previous failed to resolve.
`[feature_validators:key, validators:key]`
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: ['feature_validators', 'validators']
}
```
_NOTE:* **Do NOT** use default (or custom) i18next delimiters in namespace names.
### Document title
If you want to turn on document title localization resolve Title as `I18NextTitle` imported from 'angular-i18next':
```typescript
providers: [provideI18Next(withTitle())]
```
Routes example:
```typescript
const appRoutes: Routes = [
{
path: 'error',
component: AppErrorComponent,
data: { title: 'error:error_occured' }
},
{
path: 'denied',
component: AccessDeniedComponent,
data: { title: 'error:access_denied' }
}
];
```
Ways to use I18NextService in your code:
> **Warning:** Injection of **I18NextService** is possible, but it would not consider I18NEXT_NAMESPACE and I18NEXT_SCOPE providers. There are 2 possible reasons to inject **I18NextService**: initialization and subscription to its events. In all other cases inject **I18NextPipe**.
1) **Recommended way:** Inject via **I18NEXT_SERVICE** token. By default it will inject instance of **I18NextService**.
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService)
```
2) Legacy way: Inject via type
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
private i18NextService: I18NextService)
```
### Error handling
Error handling is now configurable:
1) By default i18next promise will use NativeErrorHandlingStrategy. I18Next would be always resolve successfully. Error could be get from 'then' handler parameter.
2) Set StrictErrorHandlingStrategy to reject load promises (init, languageChange, loadNamespaces) on first load fail (this was default in v2 but changed to fit [native i18next behavior](https://github.com/Romanchuk/angular-i18next/issues/9):
```typescript
providers: [
provideI18Next(
withCustomErrorHandlingStrategy(StrictErrorHandlingStrategy)
)
]
```
### Lazy loading
Use `i18NextNamespacesGuard` in your routes to to load i18next namespace.
Note: It is not necessary to register lazy loading namespaces in global i18next options.
```
{
path: 'rich_form',
loadComponent: () => RichFormComponent,
providers: [
{
provide: I18NEXT_NAMESPACE, // namespace to start in component
useValue: 'feature.rich_form',
},
],
canActivate: [i18NextNamespacesGuard('feature.rich_form')]
}
```
Use I18NextService.loadNamespaces() method to load namespaces in code.
# Cookbook
### i18next plugin support
```typescript
import { I18NextModule, ITranslationService, I18NEXT_SERVICE } from 'angular-i18next';
// import Backend from 'i18next-xhr-backend'; //for i18next < 20.0.0
import HttpApi from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
...
i18next.use(HttpApi)
.use(LanguageDetector)
.init(i18nextOptions)
```
### Server side rendereng (SSR)
1. Provide for server:
```typescript
import { provideI18Next, withTitle } from 'angular-i18next';
import { withSSR } from 'angular-i18next/ssr';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRouting(serverRoutes),
provideI18Next(withTitle(), withSSR()),
],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
```
2. Configure i18next in `server.ts` ([Example](./apps/angular-i18next-demo/src/server.ts)):
### Auto error message for `@angular/forms`
Use `i18nextValidationMessage` directive with formControlName
```typescript
import { I18NextValidationMessageDirective } from 'angular-i18next/forms'
@Component({
imports: [I18NextValidationMessageDirective]
})
```
There is priority order for validation messages:
1. namespace + `control_specific` with form hierarchy
2. namespace + Common validation key(like `required`)
3. `control_specific` with form hierarchy
4. Common validation key like `required`
Also you can interpolate `control.error` values. For example: For validator `Validators.min(1)`
```json
"min": "Minimal {{min}}. Actual: {{actual}}."
```
`en.validation.json`
```json
{
"required": "Field is required.",
"pattern": "$t(validation:_fill) valid value.",
"_fill": "Please fill in",
"control_specific": {
"technicalContact": {
"firstName": {
"required": "$t(validation:_fill) technical specialist's first name."
},
"lastName": {
"required": "$t(validation:_fill) technical specialist's last name."
},
"middleName": {
"required": "$t(validation:_fill) technical specialist's patronymic."
}
}
}
}
```
### Testing
```typescript
import { withSSR } from 'angular-i18next/testing';
TestBed.configureTestingModule({
imports: [ProjectComponent],
providers: [
provideI18NextMockAppInitializer(),
provideI18Next(withMock())
]
});
```
# What to do if... ?
## New angular version released, but angular-i18next is not released YET
Angular releases mostly don't break angular-i18next, but we cannot tell ahead that current version of `angular-i18next` will work correctly with latest angular version.
You can override an angular-i18next `peerDependencies` in your `package.json` on your **own risk**:
```json
"overrides": {
"angular-i18next": {
"@angular/common": "*",
"@angular/core": "*",
"@angular/platform-browser": "*"
}
}
```
# In-project testing
You might want to unit-test project components that are using i18next pipes
Example tests setup:
[libs/angular-i18next/src/tests/projectTests](https://github.com/Romanchuk/angular-i18next/tree/master/libs/angular-i18next/src/tests/projectTests)
# Demo
[Live DEMO](https://angular-i18next.onrender.com)
Demo app source code available here:
# Articles
- [Angular L10n with I18next](https://phrase.com/blog/posts/angular-l10n-with-i18next/)
- [Best Libraries for Angular I18n](https://phrase.com/blog/posts/best-libraries-for-angular-i18n/)
================================================
FILE: README_DEPRECATED.md
================================================
[](https://badge.fury.io/js/angular-i18next)
[](https://npmjs.org/package/angular-i18next)
[](https://travis-ci.com/Romanchuk/angular-i18next)
[](https://coveralls.io/github/Romanchuk/angular-i18next?branch=master)
[](http://commitizen.github.io/cz-cli/)
[](https://www.paypal.com/paypalme2/sergeyromanchuk/10USD)
[](https://github.com/romanchuk/angular-i18next)
# angular-i18next
[i18next](http://i18next.com/) v8.4+ integration with [angular](https://angular.io/) v2.0+
[Live DEMO](https://romanchuk.github.io/angular-i18next/)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Cookbook](#cookbook)
- [Deep integration](#deep-integration)
- [In-project testing](#in-project-testing)
- [Demo](#demo)
- [Articles](#articles)
- [Support project](#support-on-beerpay)
# Features
- Native i18next [options](https://www.i18next.com/configuration-options.html)
- Promise initialization
- [i18next plugin](https://www.i18next.com/plugins-and-utils.html#plugins) support
- Events support
- Namespaces lazy load
- i18next native [format](https://www.i18next.com/api.html#format) support
- document.title localization
- Error handling strategies
- i18next namespaces and scopes (prefixes) for angular modules and components
- AOT support
- [Angular Package Format](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview) support
[Related packages](#deep-integration) also has implementations for:
- Reactive forms validators localization
- Http error message localizer
# Cheers!
Hey dude! Help me out for a couple of :beers:!
Поддержи проект - угости автора кружечкой пива!
[](https://www.paypal.com/paypalme2/sergeyromanchuk/10USD)
# Installation
**1.** Install package
```
npm install i18next --save
npm install angular-i18next --save
```
**2.** Import I18NextModule to AppModule
```typescript
import { I18NextModule } from 'angular-i18next';
@NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent
],
import: [
I18NextModule.forRoot()
]
})
export class AppModule {}
```
**3.** Import I18NextModule.forRoot() to AppModule and setup provider with "init" method (use native [options](https://www.i18next.com/overview/configuration-options)). Angular would not load until i18next initialize event fired
> **Warning:**: options in example valid for i18next v20 (Always check latest API options of i18next)
```typescript
export function appInit(i18next: ITranslationService) {
return () => i18next.init({
supportedLngs: ['en', 'ru'],
fallbackLng: 'en',
debug: true,
returnEmptyString: false,
ns: [
'translation',
'validation',
'error'
],
});
}
export function localeIdFactory(i18next: ITranslationService) {
return i18next.language;
}
export const I18N_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [I18NEXT_SERVICE],
multi: true
},
{
provide: LOCALE_ID,
deps: [I18NEXT_SERVICE],
useFactory: localeIdFactory
}];
```
```typescript
@NgModule({
imports: [
...
I18NextModule.forRoot()
],
providers: [
...
I18N_PROVIDERS,
],
bootstrap: [AppComponent]
})
export class AppModule {
}
```
# Usage
### Pipes
Use "i18next" pipe to translate key:
Trigger native i18next [format method](https://www.i18next.com/formatting.html) by using I18NextFormatPipe or I18NextPipe with option 'format':
`{{ 'any_key' | i18next | i18nextFormat }}`
`{{ 'any_key' | i18next: { format: 'cap' } }}`
`{{ 'any_key' | i18nextCap }}`
**Note:** Using "i18nextCap" you will get the same result as `i18next: { format: 'cap' }`
**REMEMBER** that format will not work until you set "interpolation.format" function in i18next options.
I18NextModule has static method `static interpolationFormat(customFormat: Function = null): Function` that can be used as default interpolation format function (it provides 'upper', 'cap' and 'lower' formatters). You also can pass your custom function to be called after I18NextModule formatters:
```typescript
const i18nextOptions = {
supportedLngs: ['en', 'ru'],
ns: [
'translation',
'validation',
'error',
],
interpolation: {
format: I18NextModule.interpolationFormat((value, format, lng) => {
if(value instanceof Date)
return moment(value).format(format);
return value;
});
// format: I18NextModule.interpolationFormat()
}
};
```
**i18nextEager pipe**
This is the impure analog of *i18next pipe* that is subscribed to language change, it will change string right away to choosen language (without reloading page).
**Warning!**: Use i18nextEager only in combine with [OnPush change detection strategy](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4), or else (default change detection) each pipe will retrigger more than one time (cause of performance issues).
Subscribing to event observables:
```typescript
this.i18NextService.events.languageChanged.subscribe(lang => {
// do something
})
```
Add a provider to module/component if you want to prefix child i18next keys:
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: 'feature' // set 'feature:' prefix
}
```
```typescript
{
provide: I18NEXT_SCOPE,
useValue: 'person' // set 'person.' prefix
}
```
Since v3.1.0+ it is possible to pass array of namespaces (or scopes). [Key would fallback](https://www.i18next.com/api.html#t) to next namespace in array if the previous failed to resolve.
`[feature_validators:key, validators:key]`
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: ['feature_validators', 'validators']
}
```
_NOTE:_ **Do NOT** use default (or custom) i18next delimiters in namespace names.
### Document title
If you want to turn on document title localization resolve Title as `I18NextTitle` imported from 'angular-i18next':
```typescript
{
provide: Title,
useClass: I18NextTitle
}
```
Also you can implement your own Title service with specific behavior. Inject `I18NextPipe` (or `I18NextService`) to service/component:
```typescript
import { Injectable, Inject } from '@angular/core';
import { Title, DOCUMENT } from '@angular/platform-browser';
import { I18NextPipe } from 'angular-i18next';
@Injectable()
export class I18NextTitle extends Title {
constructor(private i18nextPipe: I18NextPipe, @Inject(DOCUMENT) doc) {
super(doc);
}
setTitle(value: string) {
return super.setTitle(this.translate(value));
}
private translate(text: string) {
return this.i18nextPipe.transform(text, { format: 'cap'});
}
}
```
Ways to use I18NextService in your code:
> **Warning:** Injection of **I18NextService** is possible, but it would not consider I18NEXT_NAMESPACE and I18NEXT_SCOPE providers. There are 2 possible reasons to inject **I18NextService**: initialization and subscription to its events. In all other cases inject **I18NextPipe**.
1) **Recommended way:** Inject via **I18NEXT_SERVICE** token. By default it will inject instance of **I18NextService**.
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService)
```
2) Legacy way: Inject via type
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
private i18NextService: I18NextService)
```
### Error handling
Error handling is now configurable:
1) By default i18next promise will use NativeErrorHandlingStrategy. I18Next would be always resolve succesfully. Error could be get from 'then' handler parameter.
2) Set StrictErrorHandlingStrategy to reject load promises (init, languageChange, loadNamespaces) on first load fail (this was default in v2 but changed to fit [native i18next behavior](https://github.com/Romanchuk/angular-i18next/issues/9):
`I18NextModule.forRoot({ errorHandlingStrategy: StrictErrorHandlingStrategy })`
### Lazy loading
Use I18NEXT_NAMESPACE_RESOLVER in your routes to to load i18next namespace.
Note: It is not neccesary to register lazy loading namespaces in global i18next options.
```
{
path: 'rich_form',
loadChildren: 'app/features/rich_form_feature/RichFormFeatureModule#RichFormFeatureModule',
data: {
i18nextNamespaces: ['feature.rich_form']
},
resolve: {
i18next: I18NEXT_NAMESPACE_RESOLVER
}
},
```
Use I18NextService.loadNamespaces() method to load namespaces in code.
# Cookbook
### i18next plugin support
```typescript
import { I18NextModule, ITranslationService, I18NEXT_SERVICE } from 'angular-i18next';
// import Backend from 'i18next-xhr-backend'; //for i18next < 20.0.0
import HttpApi from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
...
i18next.use(HttpApi)
.use(LanguageDetector)
.init(i18nextOptions)
```
### Initialize i18next before angular application
Angular would not load until i18next initialize event fired
```typescript
export function appInit(i18next: ITranslationService) {
return () => i18next.init();
}
export function localeIdFactory(i18next: ITranslationService) {
return i18next.language;
}
export const I18N_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [I18NEXT_SERVICE],
multi: true
},
{
provide: LOCALE_ID,
deps: [I18NEXT_SERVICE],
useFactory: localeIdFactory
}];
```
### Document title update on language or route change
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService) {
// page title subscription
// https://toddmotto.com/dynamic-page-titles-angular-2-router-events#final-code
this.router.events
.filter(event => event instanceof NavigationEnd)
.map(() => this.router.routerState.root)
.map(route => {
while (route.firstChild) route = route.firstChild;
return route;
})
.filter(route => route.outlet === 'primary')
.mergeMap(route => route.data)
.subscribe((event) => this.updatePageTitle(event['title']));
}
ngOnInit() {
this.i18NextService.events.languageChanged.subscribe(lang => {
let root = this.router.routerState.root;
if (root != null && root.firstChild != null) {
let data: any = root.firstChild.data;
this.updatePageTitle(data && data.value && data.value.title);
}
});
}
updatePageTitle(title: string): void {
let newTitle = title || 'application_title';
this.title.setTitle(newTitle);
}
}
```
Routes example:
```typescript
const appRoutes: Routes = [
{
path: 'error',
component: AppErrorComponent,
data: { title: 'error:error_occured' }
},
{
path: 'denied',
component: AccessDeniedComponent,
data: { title: 'error:access_denied' }
}
];
```
# What to do if... ?
## New angular version released, but angular-i18next is not released YET!!!
Angular releases mostly don't break angular-i18next, but we cannot tell ahead that current version of `angular-i18next` will work correctly with latest angular version.
You can override an angular-i18next `peerDependencies` in your `package.json` on your **own risk**:
```json
"overrides": {
"angular-i18next": {
"@angular/common": "*",
"@angular/core": "*",
"@angular/platform-browser": "*"
}
}
```
# Deep integration
List of packages to integrate angular and i18next more deeply:
- [angular-validation-message](https://github.com/Romanchuk/angular-validation-message) - angular [reactive form validators](https://angular.io/guide/reactive-forms#step-2-making-a-field-required) integration (and [angular-validation-message-i18next ](https://github.com/Romanchuk/angular-validation-message-i18next) is i18next bridge to it). It gives you possibility to localize form validators and it automatically puts localized validator error message to markup (if there is one).
- [angular-i18next-error-interceptor](https://github.com/LCGroupIT/angular-i18next-error-interceptor) - allows you to set default errot messages for non-200 http status responses. So if the back-end didn't specify { message: 'some error' } in a response (sort of contract with our backend) interceptor will check response status code and will fill { message: 'Server is not available. Please try again.' }. Also package includes pipe where you can pass HttpErrorResponse and it will return error message whenever it's back-end message or our localized message.
# In-project testing
You might want to unit-test project components that are using i18next pipes
Example tests setup:
[libs/angular-i18next/src/tests/projectTests](https://github.com/Romanchuk/angular-i18next/tree/master/libs/angular-i18next/src/tests/projectTests)
# Demo
[Live DEMO](https://romanchuk.github.io/angular-i18next-demo/)
Demo app source code available here: https://github.com/Romanchuk/angular-i18next-demo
# Articles
- [Angular L10n with I18next](https://phrase.com/blog/posts/angular-l10n-with-i18next/)
- [Best Libraries for Angular I18n](https://phrase.com/blog/posts/best-libraries-for-angular-i18n/)
================================================
FILE: apps/angular-i18next-demo/.browserslistrc
================================================
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
================================================
FILE: apps/angular-i18next-demo/.eslintrc.json
================================================
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}
================================================
FILE: apps/angular-i18next-demo/jest.config.ts
================================================
/* eslint-disable */
export default {
displayName: 'angular-i18next-demo',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['/src/test-setup.ts'],
globals: {},
coverageDirectory: '../../coverage/apps/angular-i18next-demo',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
================================================
FILE: apps/angular-i18next-demo/project.json
================================================
{
"name": "angular-i18next-demo",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/angular-i18next-demo/src",
"prefix": "angular-i18next",
"tags": [],
"targets": {
"build": {
"executor": "@nx/angular:application",
"outputs": ["{options.outputPath}"],
"options": {
"browser": "apps/angular-i18next-demo/src/main.ts",
"outputPath": "dist/angular-i18next-demo",
"index": "apps/angular-i18next-demo/src/index.html",
"tsConfig": "apps/angular-i18next-demo/tsconfig.app.json",
"assets": [
"apps/angular-i18next-demo/src/assets/favicon.png",
"apps/angular-i18next-demo/src/assets",
"apps/angular-i18next-demo/src/locales"
],
"styles": ["apps/angular-i18next-demo/src/styles.css"],
"scripts": [],
"server": "apps/angular-i18next-demo/src/main.server.ts",
"ssr": {
"entry": "apps/angular-i18next-demo/src/server.ts"
},
"outputMode": "server"
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "apps/angular-i18next-demo/src/environments/environment.ts",
"with": "apps/angular-i18next-demo/src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@nx/angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angular-i18next-demo:build:production"
},
"development": {
"buildTarget": "angular-i18next-demo:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "angular-i18next-demo:build"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"options": {
"lintFilePatterns": [
"apps/angular-i18next-demo/**/*.ts",
"apps/angular-i18next-demo/**/*.html"
]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/angular-i18next-demo"],
"options": {
"jestConfig": "apps/angular-i18next-demo/jest.config.ts",
"passWithNoTests": true
}
}
}
}
================================================
FILE: apps/angular-i18next-demo/src/app/app.component.css
================================================
================================================
FILE: apps/angular-i18next-demo/src/app/app.component.html
================================================
================================================
FILE: apps/angular-i18next-demo/src/lib/validation/services/ValidationDirtyChecker.ts
================================================
import { Injectable } from '@angular/core';
import { FormArray, FormGroup } from '@angular/forms';
@Injectable()
export class ValidationDirtyChecker {
markControlsDirty(group: FormGroup | FormArray) {
const controls = group.controls;
for (const ck in controls) {
// eslint-disable-next-line no-prototype-builtins
if (controls.hasOwnProperty(ck)) {
const c = (controls)[ck];
c.markAsDirty({ onlySelf: true });
if (c instanceof FormGroup || c instanceof FormArray)
this.markControlsDirty(c);
}
}
}
}
================================================
FILE: apps/angular-i18next-demo/src/lib/validation/validators/ArrayValidators.js
================================================
var ArrayValidators = (function () {
function ArrayValidators() {
}
ArrayValidators.minLength = function (minLength, ignoreNullAndUndefined) {
if (ignoreNullAndUndefined === void 0) { ignoreNullAndUndefined = false; }
return function (control) {
if (control) {
var isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
var val = control.value;
var isValid = false;
if (!ignoreNullAndUndefined)
isValid = val.length >= minLength;
else
isValid = val.filter(function (v) { return v != null; }).length >= minLength;
if (isValid)
return null;
return { 'arrayMinLength': minLength };
}
};
};
ArrayValidators.maxLength = function (maxLength, ignoreNullAndUndefined) {
if (ignoreNullAndUndefined === void 0) { ignoreNullAndUndefined = false; }
return function (control) {
if (control) {
var isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
var val = control.value;
var isValid = false;
if (!ignoreNullAndUndefined)
isValid = val.length <= maxLength;
else
isValid = val.filter(function (v) { return v != null; }).length <= maxLength;
if (isValid)
return null;
return { 'arrayMaxLength': maxLength };
}
};
};
ArrayValidators.eqLength = function (length, ignoreNullAndUndefined) {
if (ignoreNullAndUndefined === void 0) { ignoreNullAndUndefined = false; }
return function (control) {
if (control) {
var isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
var val = control.value;
var isValid = false;
if (!ignoreNullAndUndefined)
isValid = val.length === length;
else
isValid = val.filter(function (v) { return v != null; }).length === length;
if (isValid)
return null;
return { 'arrayMaxLength': length };
}
};
};
return ArrayValidators;
}());
export { ArrayValidators };
//# sourceMappingURL=ArrayValidators.js.map
================================================
FILE: apps/angular-i18next-demo/src/lib/validation/validators/ArrayValidators.ts
================================================
import { OnChanges, SimpleChanges, Component, Directive, forwardRef } from '@angular/core';
import { Validator, AsyncValidatorFn, ValidatorFn, FormControl, FormGroup, FormArray, AbstractControl, NG_VALIDATORS } from '@angular/forms';
export class ArrayValidators {
static minLength(minLength: Number, ignoreNullAndUndefined: Boolean = false): ValidatorFn {
return (control: FormControl) => {
if (control) {
let isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
let val: Array = control.value;
let isValid: Boolean = false;
if (!ignoreNullAndUndefined)
isValid = val.length >= minLength;
else
isValid = val.filter(v => v != null).length >= minLength;
if (isValid)
return null;
return { 'arrayMinLength': minLength };
}
};
}
static maxLength(maxLength: Number, ignoreNullAndUndefined: Boolean = false): ValidatorFn {
return (control: FormControl) => {
if (control) {
let isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
let val: Array = control.value;
let isValid: Boolean = false;
if (!ignoreNullAndUndefined)
isValid = val.length <= maxLength;
else
isValid = val.filter(v => v != null).length <= maxLength;
if (isValid)
return null;
return { 'arrayMaxLength': maxLength };
}
};
}
static eqLength(length: Number, ignoreNullAndUndefined: Boolean = false): ValidatorFn {
return (control: FormControl) => {
if (control) {
let isArray = control.value instanceof Array;
if (!isArray)
throw new Error('Control value must be array!');
let val: Array = control.value;
let isValid: Boolean = false;
if (!ignoreNullAndUndefined)
isValid = val.length === length;
else
isValid = val.filter(v => v != null).length === length;
if (isValid)
return null;
return { 'arrayMaxLength': length };
}
};
}
}
================================================
FILE: apps/angular-i18next-demo/src/lib/validation/validators/ConditionalValidator.js
================================================
import 'rxjs/add/operator/distinctUntilChanged'; // fn distinctUntilChanged
var ConditionalValidator = (function () {
function ConditionalValidator() {
}
/**
* Валидатор, который применяет валидатор при некотором заданом условии.
* @param {ConditionalFunc} conditional Условие для применения валидатора
* @param {ValidatorFn} validator Валидатор, который будет применен
* @param {Boolean} trackParentOnly Подписка только на изменение значения родителя (По-умолчанию подписка на root)
*/
ConditionalValidator.set = function (conditional, validator, trackParentOnly) {
if (trackParentOnly === void 0) { trackParentOnly = null; }
var revalidateSub;
return function (control) {
if (control && control.parent) {
if (!revalidateSub) {
revalidateOnChanges(control, trackParentOnly);
revalidateSub = true;
}
if (conditional(control.root)) {
return validator(control);
}
}
return null;
};
};
/* Не реализован */
ConditionalValidator.setAsync = function (conditional, validator) {
throw new Error('Not implemented'); // todo: implement
};
ConditionalValidator.equivalent = function (controlKey, expectedValue) {
return function (rootGroup) {
var control = rootGroup.get(controlKey);
if (!control)
return expectedValue === undefined;
return expectedValue === control.value;
};
};
return ConditionalValidator;
}());
export { ConditionalValidator };
function revalidateOnChanges(control, trackParentOnly) {
if (trackParentOnly === void 0) { trackParentOnly = null; }
var parentControl = trackParentOnly ? control.parent : control.root;
parentControl.valueChanges
.distinctUntilChanged(function (a, b) {
// These will always be plain objects coming from the form, do a simple comparison
if (a && !b || !a && b) {
return false;
}
else if (a && b && Object.keys(a).length !== Object.keys(b).length) {
return false;
}
else if (a && b) {
for (var i in a) {
if (a[i] !== b[i]) {
return false;
}
}
}
return true;
})
.subscribe(function () {
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
});
}
//# sourceMappingURL=ConditionalValidator.js.map
================================================
FILE: apps/angular-i18next-demo/src/lib/validation/validators/ConditionalValidator.ts
================================================
import { Validator, AsyncValidatorFn, ValidatorFn, FormControl, FormGroup, FormArray, AbstractControl } from '@angular/forms';
import { distinctUntilChanged } from 'rxjs/operators';
// todo: доработать ConditionalValidator, чтобы он работал в связке с асинхронным валидатором (сейчас валится)
/* usage
this.formBuilder.group({
vehicleType: ['', Validators.required],
licencePlate: [
'',
ConditionalValidator.apply(
group => group.controls.vehicleType.value === 'car',
Validators.compose([
Validators.required,
Validators.minLength(6)
])
),
]
});
this.formBuilder.group({
country: ['', Validators.required],
vehicleType: ['', Validators.required],
licencePlate: [
'',
Validators.compose([
ConditionalValidator.apply(
group => group.controls.vehicleType.value === 'car',
Validators.required
),
ConditionalValidator.apply(
group => group.controls.country.value === 'sweden',
Validators.minLength(6)
),
])
]
});
*/
interface ConditionalFunc {
(rootGroup: FormGroup | FormArray): Boolean;
}
export class ConditionalValidator {
/**
* Валидатор, который применяет валидатор при некотором заданом условии.
* @param {ConditionalFunc} conditional Условие для применения валидатора
* @param {ValidatorFn} validator Валидатор, который будет применен
* @param {Boolean} trackParentOnly Подписка только на изменение значения родителя (По-умолчанию подписка на root)
*/
static set(conditional: ConditionalFunc, validator: ValidatorFn, trackParentOnly: Boolean = null): ValidatorFn {
let revalidateSub: Boolean;
return (control: FormControl) => {
if (control && control.parent) {
if (!revalidateSub) {
revalidateOnChanges(control, trackParentOnly);
revalidateSub = true;
}
if (conditional(control.root)) {
return validator(control);
}
}
return null;
};
}
/* Не реализован */
static setAsync(conditional: Function, validator: AsyncValidatorFn): AsyncValidatorFn {
throw new Error('Not implemented'); // todo: implement
}
static equivalent(controlKey: string, expectedValue: any): ConditionalFunc {
return (rootGroup: FormGroup|FormArray) => {
let control = rootGroup.get(controlKey);
if (!control)
return expectedValue === undefined;
return expectedValue === control.value;
}
}
}
function revalidateOnChanges(control: AbstractControl, trackParentOnly: Boolean = null): void {
let parentControl = trackParentOnly ? control.parent : control.root;
parentControl.valueChanges
.pipe(
distinctUntilChanged((a, b) => {
// These will always be plain objects coming from the form, do a simple comparison
if (a && !b || !a && b) {
return false;
} else if (a && b && Object.keys(a).length !== Object.keys(b).length) {
return false;
} else if (a && b) {
for (let i in a) {
if (a[i] !== b[i]) {
return false;
}
}
}
return true;
})
)
.subscribe(() => {
control.updateValueAndValidity({ onlySelf: true, emitEvent: false });
});
}
================================================
FILE: apps/angular-i18next-demo/src/locales/en.error.json
================================================
{
"oops": "Oops!",
"error_occured": "Error has occured",
"error_occured_onload": "$t(error:error_occured)",
"access_denied": "Access is denied",
"reload": "Reload",
"restart": "Restart",
"contact_administrator_or_try_to_clear_browser_chache_and_restart_application": "Contact your administrator or clear browser cache and restart page.",
"need_help_write_to_us": "Need help? Contact us.",
"write": "Contact",
"cookies": {
"how_to": "How to clear browser cache and cookies",
"chrome_clear": "Если у вас Google Chrome, то:
Запустите Chrome.
Нажмите на значок на панели инструментов.
В меню Дополнительные инструменты нажмите Удаление данных о просмотренных страницах.
В окне \"Очистить историю\" выберите пункты Файлы cookie, а также другие данные сайтов и плагинов и Изображения и другие файлы, сохраненные в кеше.
В раскрывающемся меню в верхней части страницы выберите период, данные за который нужно удалить. Выберите вариант за все время, если вы хотите удалить все сведения.
Нажмите кнопку Очистить историю.
"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/en.feature.rich_form.json
================================================
{
"title": "Rich form with validation",
"technical_contact": "technical contact",
"count": "count",
"person": {
"first_name": "first name",
"last_name": "last name",
"middle_name": "middle name"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/en.translation.json
================================================
{
"application_title": "Demo: angular-i18next",
"intro": "This application is demonstrating itegration of i18next library with angular. You can switch language in the navbar.",
"simple_demo": "Simple demo",
"rich_form_title": "Rich form with validation",
"parametrized_string_title": "parametrized string demo",
"case_demo_title": "i18next pipe 'format' option demo",
"case_demo": "rise and shine, Mr.Freeman",
"parametrized_string": "I am parametrized sting with a value: {{value}} and a string: '{{str}}'",
"email": "email",
"_languages": {
"ru": "Русский",
"en": "English"
},
"buttons": {
"send": "send"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/en.validation.json
================================================
{
"required": "Field is required.",
"error": "Error occured.",
"min": "Minimum value is {{min}}. Was {{actual}}.",
"max": "Maximum value is {{max}}. Was {{actual}}.",
"email": "$t(validation:_fill) valid e-mail.",
"pattern": "$t(validation:_fill) valid value.",
"maxlength": "Maximum length {{requiredLength}}.",
"_fill": "Please fill in",
"control_specific": {
"technicalContact": {
"firstName": {
"required": "$t(validation:_fill) technical specialist's first name."
},
"lastName": {
"required": "$t(validation:_fill) technical specialist's last name."
},
"middleName": {
"required": "$t(validation:_fill) technical specialist's patronymic."
}
}
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/ru.error.json
================================================
{
"oops": "Упс!",
"error_occured": "произошла ошибка",
"error_occured_onload": "При загрузке приложения $t(error:error_occured)",
"access_denied": "Недостаточно прав",
"reload": "Перезагрузить",
"restart": "Перезапустить",
"contact_administrator_or_try_to_clear_browser_chache_and_restart_application": "Обратитесь к администратору либо попробуйте очистить кэш и перезапустить приложение",
"need_help_write_to_us": "Нужна помощь? Пишите нам.",
"write": "Написать",
"cookies": {
"how_to": "Как очистить кэш и удалить файлы cookie",
"chrome_clear": "Если у вас Google Chrome, то:
Запустите Chrome.
Нажмите на значок на панели инструментов.
В меню Дополнительные инструменты нажмите Удаление данных о просмотренных страницах.
В окне \"Очистить историю\" выберите пункты Файлы cookie, а также другие данные сайтов и плагинов и Изображения и другие файлы, сохраненные в кеше.
В раскрывающемся меню в верхней части страницы выберите период, данные за который нужно удалить. Выберите вариант за все время, если вы хотите удалить все сведения.
Нажмите кнопку Очистить историю.
"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/ru.feature.rich_form.json
================================================
{
"title": "Форма с валидацией",
"technical_contact": "технический специалист",
"count": "кол-во",
"person": {
"first_name": "имя",
"last_name": "фамилия",
"middle_name": "отчество"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/ru.translation.json
================================================
{
"application_title": "Демо: angular-i18next",
"intro": "Данное приложение демонстрирует интеграцию библиотеки i18next с angular. Вы можете сменить язык в шапке.",
"simple_demo": "простое демо",
"rich_form_title": "Форма с валидацией",
"parametrized_string_title": "демонстрация параметризованной строки",
"case_demo_title": "демонстрация опции 'format'",
"case_demo": "проснись и пой, мистер Фримэн",
"parametrized_string": "Я параметризованная строка значением: {{value}} и строкой: '{{str}}'",
"email": "email адрес",
"_languages": {
"ru": "Русский",
"en": "English"
},
"buttons": {
"send": "отправить"
}
}
================================================
FILE: apps/angular-i18next-demo/src/locales/ru.validation.json
================================================
{
"required": "Заполните это поле.",
"error": "Возникла ошибка.",
"minValue": "Минимальное значение.",
"maxValue": "Максимальное значение.",
"min": "Минимальное значение: {{min}}. Текущее: {{actual}}.",
"max": "Максимальное значение: {{max}}. Текущее: {{actual}}.",
"email": "Введите валидный email.",
"pattern": "Введите валидное значение.",
"maxlength": "Максимальная длина {{requiredLength}}.",
"control_specific": {
"technicalContact": {
"firstName": {
"required": "Заполните имя технического специалиста.",
"pattern": "Имя технического специалиста содержит недопустимые символы."
},
"lastName": {
"required": "Заполните фамилию технического специалиста.",
"pattern": "Фамилия технического специалиста содержит недопустимые символы."
},
"middleName": {
"required": "Заполните отчество технического специалиста.",
"pattern": "Отчество технического специалиста содержит недопустимые символы."
}
}
}
}
================================================
FILE: apps/angular-i18next-demo/src/main.server.ts
================================================
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';
const bootstrap = (context: BootstrapContext) => bootstrapApplication(AppComponent, config, context);
export default bootstrap;
================================================
FILE: apps/angular-i18next-demo/src/main.ts
================================================
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
function bootstrap() {
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
}
if (document.readyState === 'complete') {
bootstrap();
} else {
document.addEventListener('DOMContentLoaded', bootstrap);
}
================================================
FILE: apps/angular-i18next-demo/src/server.ts
================================================
import {
AngularNodeAppEngine,
createNodeRequestHandler,
isMainModule,
writeResponseToNodeResponse,
} from '@angular/ssr/node';
import type { NextFunction, Request, Response } from 'express';
import express from 'express';
import i18next from 'i18next';
import ChainedBackend, { ChainedBackendOptions } from 'i18next-chained-backend';
import * as i18nextHttpMiddleware from 'i18next-http-middleware';
import resourcesToBackend from "i18next-resources-to-backend";
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import i18nextOptions from './app/i18next.options';
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
await i18next
.use(ChainedBackend)
.use(i18nextHttpMiddleware.LanguageDetector)
.init({
...i18nextOptions,
backend: {
backends: [
resourcesToBackend((lng, ns, clb) => {
import(`./locales/${lng}.${ns}.json`)
.then((resources) => clb(null, resources))
.catch((r)=> clb(r,null))
})
],
backendOptions: [{
loadPath: '/locales/{{lng}}.{{ns}}.json'
}]
}
});
const i18nextHandler = i18nextHttpMiddleware.handle(i18next) as any;
app.use(i18nextHandler);
/**
* Example Express Rest API endpoints can be defined here.
* Uncomment and define endpoints as necessary.
*
* Example:
* ```ts
* app.get('/api/**', (req, res) => {
* // Handle API request
* });
* ```
*/
/**
* Serve static files from /browser
*/
app.use(
express.static(browserDistFolder, {
maxAge: '1y',
index: false,
redirect: false,
}),
);
/**
* Handle all other requests by rendering the Angular application.
*/
app.use('/**', (req: Request & i18nextHttpMiddleware.I18NextRequest, res: Response, next: NextFunction) => {
angularApp
.handle(req, {
i18n: req.i18n,
})
.then((response) =>
response ? writeResponseToNodeResponse(response, res) : next(),
)
.catch(next);
});
/**
* Start the server if this module is the main entry point.
* The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.
*/
if (isMainModule(import.meta.url) || process.env['PM2'] === 'true') {
const port = process.env['PORT'] || 4000;
const server = app.listen(port, () => {
process.send?.('ready');
console.log(`Node Express server listening on http://localhost:${port}`);
});
// Graceful shutdown
process.on('SIGINT', () => {
const cleanUp = () => {
// Clean up other resources like DB connections
}
console.log('Closing server...')
server.close(() => {
console.log('Server closed !!! ')
cleanUp()
process.exit()
})
// Force close server after 5secs
setTimeout((e: any) => {
console.log('Forcing server close !!!', e)
cleanUp()
process.exit(1)
}, 5000)
})
}
/**
* The request handler used by the Angular CLI (dev-server and during build).
*/
export const reqHandler = createNodeRequestHandler(app);
================================================
FILE: apps/angular-i18next-demo/src/styles.css
================================================
/* You can add global styles to this file, and also import other style files */
input.ng-dirty.ng-invalid,
textarea.ng-dirty.ng-invalid,
.form-control.ng-dirty.ng-invalid,
.ng-dirty.ng-invalid:focus
{
background-color: #FDEDED;
border-color: #D22630;
-webkit-box-shadow: none;
box-shadow: none;
}
/* s7 ui kit fix */
.form-group .error-container {
display: block;
}
.error-container {
color: #D22630;
padding-top: 2px;
}
/* end fix */
checkbox.ng-invalid .custom-control-indicator,
multiplecheckbox.ng-invalid.ng-dirty .custom-control-indicator,
flatpickr.ng-invalid.ng-dirty .form-control,
datepicker.ng-invalid.ng-dirty .select2-container .select2-selection,
radio-button.ng-dirty.ng-invalid .custom-control-indicator,
div.ng-invalid.ng-dirty.form-group-valid .custom-control-indicator,
div.ng-invalid.ng-dirty.form-group-valid .select2-container .select2-selection {
border-color: #D22630;
background-color: #FDEDED;
}
radio-button + radio-button,
checkbox + checkbox {
margin-left: 15px;
}
================================================
FILE: apps/angular-i18next-demo/src/test-setup.ts
================================================
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';
setupZonelessTestEnv();
================================================
FILE: apps/angular-i18next-demo/tsconfig.app.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"src/main.ts",
"src/main.server.ts",
"src/server.ts"
],
"include": ["src/**/*.d.ts"],
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
================================================
FILE: apps/angular-i18next-demo/tsconfig.editor.json
================================================
{
"extends": "./tsconfig.json",
"include": ["**/*.ts"],
"compilerOptions": {
"types": ["jest", "node"]
}
}
================================================
FILE: apps/angular-i18next-demo/tsconfig.json
================================================
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./tsconfig.editor.json"
}
],
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
================================================
FILE: apps/angular-i18next-demo/tsconfig.server.json
================================================
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "../../out-tsc/server",
"target": "es2019",
"types": [
"node"
],
"allowSyntheticDefaultImports": true,
},
"files": [
"src/main.server.ts",
"server.ts"
],
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
}
}
================================================
FILE: apps/angular-i18next-demo/tsconfig.spec.json
================================================
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"],
"allowSyntheticDefaultImports": true, // Typing support for this case
"esModuleInterop": true,
},
"files": ["src/test-setup.ts"],
"include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}
================================================
FILE: decorate-angular-cli.js
================================================
/**
* This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
* and faster execution of tasks.
*
* It does this by:
*
* - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
* - Symlinking the ng to nx command, so all commands run through the Nx CLI
* - Updating the package.json postinstall script to give you control over this script
*
* The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
* Every command you run should work the same when using the Nx CLI, except faster.
*
* Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
* will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
* The Nx CLI simply does some optimizations before invoking the Angular CLI.
*
* To opt out of this patch:
* - Replace occurrences of nx with ng in your package.json
* - Remove the script from your postinstall script in your package.json
* - Delete and reinstall your node_modules
*/
const fs = require('fs');
const os = require('os');
const cp = require('child_process');
const isWindows = os.platform() === 'win32';
let output;
try {
output = require('@nx/workspace').output;
} catch (e) {
console.warn(
'Angular CLI could not be decorated to enable computation caching. Please ensure @nx/workspace is installed.'
);
process.exit(0);
}
/**
* Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
* invoke the Nx CLI and get the benefits of computation caching.
*/
function symlinkNgCLItoNxCLI() {
try {
const ngPath = './node_modules/.bin/ng';
const nxPath = './node_modules/.bin/nx';
if (isWindows) {
/**
* This is the most reliable way to create symlink-like behavior on Windows.
* Such that it works in all shells and works with npx.
*/
['', '.cmd', '.ps1'].forEach((ext) => {
if (fs.existsSync(nxPath + ext))
fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
});
} else {
// If unix-based, symlink
cp.execSync(`ln -sf ./nx ${ngPath}`);
}
} catch (e) {
output.error({
title:
'Unable to create a symlink from the Angular CLI to the Nx CLI:' +
e.message,
});
throw e;
}
}
try {
symlinkNgCLItoNxCLI();
require('@nrwl/cli/lib/decorate-cli').decorateCli();
output.log({
title: 'Angular CLI has been decorated to enable computation caching.',
});
} catch (e) {
output.error({
title: 'Decoration of the Angular CLI did not complete successfully',
});
}
================================================
FILE: ecosystem.config.js
================================================
const { cwd } = require("process");
module.exports = {
apps: [
{
name: 'angular-i18next-demo',
script: 'server.mjs',
cwd: './dist/angular-i18next-demo/server',
max_memory_restart: '100M',
env: {
PM2: true,
NODE_ENV: "development"
},
env_production: {
PM2: true,
NODE_ENV: "production",
}
},
],
};
================================================
FILE: jest.config.ts
================================================
export default {
projects: ['./libs/**/jest.config.ts'],
};
================================================
FILE: jest.preset.js
================================================
const nxPreset = require('@nx/jest/preset').default;
module.exports = {
...nxPreset,
/* TODO: Update to latest Jest snapshotFormat
* By default Nx has kept the older style of Jest Snapshot formats
* to prevent breaking of any existing tests with snapshots.
* It's recommend you update to the latest format.
* You can do this by removing snapshotFormat property
* and running tests with --update-snapshot flag.
* Example: "nx affected --targets=test --update-snapshot"
* More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format
*/
snapshotFormat: { escapeString: true, printBasicPrototype: true },
};
================================================
FILE: libs/angular-i18next/.eslintrc.json
================================================
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"]
}
================================================
FILE: libs/angular-i18next/CHANGELOG.md
================================================
# [21.0.0-3](https://github.com/Romanchuk/angular-i18next/compare/v20.0.1...v21.0.0-3) (2026-01-23)
### Bug Fixes
* build ([4f8d746](https://github.com/Romanchuk/angular-i18next/commit/4f8d746e88401bc339250af3383097f6837823cb))
* jest test setup ([253a5eb](https://github.com/Romanchuk/angular-i18next/commit/253a5eb47f55428e81ec43c0c48fff2de3036205))
* units ([2a46eda](https://github.com/Romanchuk/angular-i18next/commit/2a46eda0df9e117357965e7499573a88992fef5b))
# [21.0.0-2](https://github.com/Romanchuk/angular-i18next/compare/v20.0.1...v21.0.0-2) (2026-01-23)
### Bug Fixes
* build ([4f8d746](https://github.com/Romanchuk/angular-i18next/commit/4f8d746e88401bc339250af3383097f6837823cb))
* jest test setup ([253a5eb](https://github.com/Romanchuk/angular-i18next/commit/253a5eb47f55428e81ec43c0c48fff2de3036205))
* units ([2a46eda](https://github.com/Romanchuk/angular-i18next/commit/2a46eda0df9e117357965e7499573a88992fef5b))
# [21.0.0-1](https://github.com/Romanchuk/angular-i18next/compare/v20.0.1...v21.0.0-1) (2026-01-23)
### Bug Fixes
* build ([4f8d746](https://github.com/Romanchuk/angular-i18next/commit/4f8d746e88401bc339250af3383097f6837823cb))
* jest test setup ([253a5eb](https://github.com/Romanchuk/angular-i18next/commit/253a5eb47f55428e81ec43c0c48fff2de3036205))
* units ([2a46eda](https://github.com/Romanchuk/angular-i18next/commit/2a46eda0df9e117357965e7499573a88992fef5b))
## [20.0.1](https://github.com/Romanchuk/angular-i18next/compare/v20.0.1-0...v20.0.1) (2025-11-14)
## [20.0.1-0](https://github.com/Romanchuk/angular-i18next/compare/v20.0.0...v20.0.1-0) (2025-11-14)
### Bug Fixes
* i18next typings ([9dabeb9](https://github.com/Romanchuk/angular-i18next/commit/9dabeb9ab832413bb488151eb6f74bd65c9a48e8))
# [20.0.0](https://github.com/Romanchuk/angular-i18next/compare/v19.1.1...v20.0.0) (2025-08-22)
### Features
* **up version i18next 25:** up version i18next 25 ([4a8d23a](https://github.com/Romanchuk/angular-i18next/commit/4a8d23a016ca4daa5bf06255b24ec10faa004948))
### BREAKING CHANGES
* **up version i18next 25:** up version i18next 25
## [19.1.1](https://github.com/Romanchuk/angular-i18next/compare/v19.1.1-0...v19.1.1) (2025-08-22)
## [19.1.1-0](https://github.com/Romanchuk/angular-i18next/compare/v19.1.0...v19.1.1-0) (2025-06-22)
### Bug Fixes
* ci ([f5c5339](https://github.com/Romanchuk/angular-i18next/commit/f5c5339598c340c2e7f973f90f06dd73c85c621b))
* ci ([563993b](https://github.com/Romanchuk/angular-i18next/commit/563993b507873903a6caf4374508686d8b1b5fd0))
* ci ([443db4e](https://github.com/Romanchuk/angular-i18next/commit/443db4e748522519bd1057947651e724187b1392))
* prepare demo ([a80d2bc](https://github.com/Romanchuk/angular-i18next/commit/a80d2bc19cfed95c5c5a7c4cf40a560f720353af))
* tsconfig.spec ([cea853b](https://github.com/Romanchuk/angular-i18next/commit/cea853bdab70b7199f05896a43deed9c26e26088))
# [19.1.0](https://github.com/Romanchuk/angular-i18next/compare/v19.0.1...v19.1.0) (2025-03-03)
### Bug Fixes
* browser build and serve ([d9b73f2](https://github.com/Romanchuk/angular-i18next/commit/d9b73f20b82f5ba1d0692c40787e47032f30367e))
* build ([300e975](https://github.com/Romanchuk/angular-i18next/commit/300e975a1c6072e0564c5fff50e4bf1ddb7f4751))
* build ([ed46b54](https://github.com/Romanchuk/angular-i18next/commit/ed46b542564070d462b8d5b3c0b92b1e4a04ac55))
* forms ([3781a6b](https://github.com/Romanchuk/angular-i18next/commit/3781a6b7786b4666a77402cefec13860dacc4b05))
* new provide ([f17a4f8](https://github.com/Romanchuk/angular-i18next/commit/f17a4f863b422c667d76457bc4c996760a3c1ca0))
* specs ([5e12691](https://github.com/Romanchuk/angular-i18next/commit/5e12691a4dc091f251d0e254f36d8fa568a22158))
* ssr ([822b4d1](https://github.com/Romanchuk/angular-i18next/commit/822b4d1950acdadc2a5dabcbdd3a9983d9b15acd))
* tests ([7bba2bc](https://github.com/Romanchuk/angular-i18next/commit/7bba2bc353eab68a867c8bef62e7da28b9557f58))
# [19.1.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v19.0.1...v19.1.0-beta) (2025-03-01)
### Bug Fixes
* browser build and serve ([d9b73f2](https://github.com/Romanchuk/angular-i18next/commit/d9b73f20b82f5ba1d0692c40787e47032f30367e))
* build ([300e975](https://github.com/Romanchuk/angular-i18next/commit/300e975a1c6072e0564c5fff50e4bf1ddb7f4751))
* build ([ed46b54](https://github.com/Romanchuk/angular-i18next/commit/ed46b542564070d462b8d5b3c0b92b1e4a04ac55))
* forms ([3781a6b](https://github.com/Romanchuk/angular-i18next/commit/3781a6b7786b4666a77402cefec13860dacc4b05))
* new provide ([f17a4f8](https://github.com/Romanchuk/angular-i18next/commit/f17a4f863b422c667d76457bc4c996760a3c1ca0))
* specs ([5e12691](https://github.com/Romanchuk/angular-i18next/commit/5e12691a4dc091f251d0e254f36d8fa568a22158))
* ssr ([822b4d1](https://github.com/Romanchuk/angular-i18next/commit/822b4d1950acdadc2a5dabcbdd3a9983d9b15acd))
* tests ([7bba2bc](https://github.com/Romanchuk/angular-i18next/commit/7bba2bc353eab68a867c8bef62e7da28b9557f58))
## [19.0.1](https://github.com/Romanchuk/angular-i18next/compare/v19.0.0...v19.0.1) (2025-01-11)
# [19.0.0](https://github.com/Romanchuk/angular-i18next/compare/v19.0.0-0...v19.0.0) (2025-01-11)
### Bug Fixes
* app ([b634439](https://github.com/Romanchuk/angular-i18next/commit/b63443967f5da8bb470c09871562d431a2af2cf3))
# [19.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v18.0.0...v19.0.0-0) (2025-01-11)
### Bug Fixes
* pages ([27d6e56](https://github.com/Romanchuk/angular-i18next/commit/27d6e5644d9c9d50d88fc7ab7bdc3d23f94285ef))
* tests ([29abe20](https://github.com/Romanchuk/angular-i18next/commit/29abe20a002ec9912af0388abf4a3f5eb0a97d90))
# [18.0.0](https://github.com/Romanchuk/angular-i18next/compare/v18.0.0-0...v18.0.0) (2024-06-03)
# [18.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v17.0.2...v18.0.0-0) (2024-06-03)
## [17.0.2](https://github.com/Romanchuk/angular-i18next/compare/v17.0.1...v17.0.2) (2024-06-03)
### Bug Fixes
* tests ([50c5f38](https://github.com/Romanchuk/angular-i18next/commit/50c5f38b122755d1a33fa0db0a59f7be201f26d3))
## [17.0.1](https://github.com/Romanchuk/angular-i18next/compare/v17.0.0...v17.0.1) (2023-12-07)
### Bug Fixes
* nx and jest setup ([0e6a61d](https://github.com/Romanchuk/angular-i18next/commit/0e6a61dd882f103b40bce38577fd7e7bcf44309a))
# [17.0.0](https://github.com/Romanchuk/angular-i18next/compare/v17.0.0-1...v17.0.0) (2023-12-01)
# [17.0.0-1](https://github.com/Romanchuk/angular-i18next/compare/v17.0.0-0...v17.0.0-1) (2023-11-28)
# [17.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v16.0.0...v17.0.0-0) (2023-11-27)
# [16.0.0](https://github.com/Romanchuk/angular-i18next/compare/v16.0.0-0...v16.0.0) (2023-06-09)
# [16.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v15.0.5...v16.0.0-0) (2023-06-09)
### Bug Fixes
* package.json ([4f3d909](https://github.com/Romanchuk/angular-i18next/commit/4f3d909321651faa4f6406cf300582ce8a8001ce))
* test ([991dbca](https://github.com/Romanchuk/angular-i18next/commit/991dbca3c5ad9807b4884727065effe456f56b61))
* test script ([b075485](https://github.com/Romanchuk/angular-i18next/commit/b0754858d865a5e4aaa80ac18fb264d276f26787))
## [15.0.5](https://github.com/Romanchuk/angular-i18next/compare/v15.0.4...v15.0.5) (2023-01-24)
### Bug Fixes
* [#101](https://github.com/Romanchuk/angular-i18next/issues/101) ([e7d095a](https://github.com/Romanchuk/angular-i18next/commit/e7d095a2336b663f95e08ddadbe65de6cf8b191c))
## [15.0.4](https://github.com/Romanchuk/angular-i18next/compare/v15.0.3...v15.0.4) (2023-01-16)
## [15.0.3](https://github.com/Romanchuk/angular-i18next/compare/v15.0.1...v15.0.3) (2023-01-16)
## [15.0.2](https://github.com/Romanchuk/angular-i18next/compare/v15.0.1...v15.0.2) (2023-01-16)
## [15.0.1](https://github.com/Romanchuk/angular-i18next/compare/v15.0.0...v15.0.1) (2023-01-16)
### Bug Fixes
* [#97](https://github.com/Romanchuk/angular-i18next/issues/97) "strictNullChecks": true ([92d8205](https://github.com/Romanchuk/angular-i18next/commit/92d8205003908cb89587a99268983184fa4d6316))
* t signature ([029478a](https://github.com/Romanchuk/angular-i18next/commit/029478a5582afe626d892b8e1ee59c7d08e544f5))
# [15.0.0](https://github.com/Romanchuk/angular-i18next/compare/v15.0.0-1...v15.0.0) (2023-01-16)
# [15.0.0-1](https://github.com/Romanchuk/angular-i18next/compare/v15.0.0-0...v15.0.0-1) (2023-01-16)
# [15.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v14.2.0...v15.0.0-0) (2022-12-22)
### Bug Fixes
* pages deploy ([df17d58](https://github.com/Romanchuk/angular-i18next/commit/df17d58fcfd3a18c9862064d43e33e799effb84e))
# [14.2.0](https://github.com/Romanchuk/angular-i18next/compare/v14.2.0-1...v14.2.0) (2022-11-22)
# [14.2.0-1](https://github.com/Romanchuk/angular-i18next/compare/v14.2.0-0...v14.2.0-1) (2022-11-21)
### Bug Fixes
* factory type ([7a0c62c](https://github.com/Romanchuk/angular-i18next/commit/7a0c62cac779149c9b26b8cbd502538457d7159e))
* missed import ([e793bc5](https://github.com/Romanchuk/angular-i18next/commit/e793bc511227f887d677bd2f45f3b2b173977a8e))
* specs default import ([532dd94](https://github.com/Romanchuk/angular-i18next/commit/532dd94bc23f57f0e3e0256a3534f0c48381f12f))
# [14.2.0-0](https://github.com/Romanchuk/angular-i18next/compare/v14.1.0...v14.2.0-0) (2022-11-17)
### Bug Fixes
* i18next instance ([93e48b6](https://github.com/Romanchuk/angular-i18next/commit/93e48b646c486d9e157447a704d88925e05957c6))
* jest default import ([cea6677](https://github.com/Romanchuk/angular-i18next/commit/cea66776dd6af912dfdfff500f14bbe8e17c81e7))
* link to global i18next ([4439d3a](https://github.com/Romanchuk/angular-i18next/commit/4439d3a19ef3d2bec7fcb296b8f3eaebbd1af6a8))
* tests ([78bc41e](https://github.com/Romanchuk/angular-i18next/commit/78bc41e3a4eca2b63f28513120f0ef9863d2a21a))
# [14.1.0](https://github.com/Romanchuk/angular-i18next/compare/v14.0.5-6...v14.1.0) (2022-11-09)
### Bug Fixes
* messages ([1cbebea](https://github.com/Romanchuk/angular-i18next/commit/1cbebea76d188057070442d6ffbd62bbf44ccf09))
* setup ([86de471](https://github.com/Romanchuk/angular-i18next/commit/86de471f898068274355d87f1c084ce76bb72b91))
## [14.0.5-6](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.5-6) (2022-11-07)
### Bug Fixes
* async funcs ([25e2c24](https://github.com/Romanchuk/angular-i18next/commit/25e2c24ba8c0cce1d4b235d1449fb554937b6fe4))
* build ([aafe356](https://github.com/Romanchuk/angular-i18next/commit/aafe356437287a4a2e30668a726c18e5a118cc04))
* cpy ([2a2460e](https://github.com/Romanchuk/angular-i18next/commit/2a2460e8e53f5a18185bf979318e0be77019f82e))
* Fixes i18next format call with options undefined ([ccfc6e1](https://github.com/Romanchuk/angular-i18next/commit/ccfc6e1583fcb4a5a8b591ac0e3e8cf95ce675eb))
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
* test run ([cc2ca1c](https://github.com/Romanchuk/angular-i18next/commit/cc2ca1c6dbef4fe1126de9ffb7c73f0dc2be9062))
* tests ([0b868fc](https://github.com/Romanchuk/angular-i18next/commit/0b868fc25b9c7ff4902308425e066835d117596d))
## [14.0.5-3](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.5-3) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.5-2](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.5-2) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.5-1](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.5-1) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.5-0](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.5-0) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.4-0](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.4-0) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.3](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.3) (2022-07-05)
### Bug Fixes
* package ([9b03de0](https://github.com/Romanchuk/angular-i18next/commit/9b03de01029432a679b84f0b535c8c0986a2ce49))
## [14.0.2](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.2) (2022-07-05)
## [14.0.1](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0...v14.0.1) (2022-07-05)
# [14.0.0](https://github.com/Romanchuk/angular-i18next/compare/v14.0.0-0...v14.0.0) (2022-07-04)
# [14.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v11.0.0...v14.0.0-0) (2022-06-14)
### Bug Fixes
* [#81](https://github.com/Romanchuk/angular-i18next/issues/81) ([820a9e8](https://github.com/Romanchuk/angular-i18next/commit/820a9e8ab0d1b3f0c4757c4f7096dce4e1f844ed))
# [11.0.0](https://github.com/Romanchuk/angular-i18next/compare/v11.0.0-0...v11.0.0) (2022-01-28)
# [11.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v10.3.0...v11.0.0-0) (2022-01-04)
### Bug Fixes
* np dist ([670004f](https://github.com/Romanchuk/angular-i18next/commit/670004f2b1c41e0de88708767563b960bd88a3e6))
# [10.3.0](https://github.com/Romanchuk/angular-i18next/compare/v10.3.0-0...v10.3.0) (2021-06-15)
# [10.3.0-0](https://github.com/Romanchuk/angular-i18next/compare/v10.2.0...v10.3.0-0) (2021-06-15)
# [10.2.0](https://github.com/Romanchuk/angular-i18next/compare/v10.2.0-0...v10.2.0) (2021-05-12)
# [10.2.0-0](https://github.com/Romanchuk/angular-i18next/compare/v10.1.0...v10.2.0-0) (2021-05-12)
### Features
* i18next v20+ support ([0327a7c](https://github.com/Romanchuk/angular-i18next/commit/0327a7c9f35140f0c8e098d9d1528b6e7303a8d0))
# [10.1.0](https://github.com/Romanchuk/angular-i18next/compare/v10.1.0-0...v10.1.0) (2021-03-01)
# [10.1.0-0](https://github.com/Romanchuk/angular-i18next/compare/v10.0.1...v10.1.0-0) (2021-03-01)
### Bug Fixes
* **I18NextEagerPipe:** ensure changing PipeOptions returns correct translated value a not cached one with different PipeOptions but same key ([4a6d375](https://github.com/Romanchuk/angular-i18next/commit/4a6d375181dda41399c58f7644b97d3755acf84f))
## [10.0.1](https://github.com/Romanchuk/angular-i18next/compare/v10.0.1-beta...v10.0.1) (2020-12-21)
## [10.0.1-beta](https://github.com/Romanchuk/angular-i18next/compare/v10.0.0...v10.0.1-beta) (2020-12-21)
# [10.0.0](https://github.com/Romanchuk/angular-i18next/compare/v10.0.0-2...v10.0.0) (2020-07-06)
# [10.0.0-2](https://github.com/Romanchuk/angular-i18next/compare/v10.0.0-1...v10.0.0-2) (2020-07-06)
# [10.0.0-1](https://github.com/Romanchuk/angular-i18next/compare/v10.0.0-0...v10.0.0-1) (2020-07-06)
# [10.0.0-0](https://github.com/Romanchuk/angular-i18next/compare/v9.0.1...v10.0.0-0) (2020-07-06)
## [9.0.1](https://github.com/Romanchuk/angular-i18next/compare/v9.0.0...v9.0.1) (2020-02-25)
### Bug Fixes
* pass translate options ([4cfe42c](https://github.com/Romanchuk/angular-i18next/commit/4cfe42c))
# [9.0.0](https://github.com/Romanchuk/angular-i18next/compare/v8.1.0-beta.3...v9.0.0) (2020-02-20)
# [8.1.0-beta.3](https://github.com/Romanchuk/angular-i18next/compare/v8.1.0-beta.2...v8.1.0-beta.3) (2020-02-20)
# [8.1.0-beta.2](https://github.com/Romanchuk/angular-i18next/compare/v8.1.0-beta.1...v8.1.0-beta.2) (2020-02-20)
# [8.1.0-beta.1](https://github.com/Romanchuk/angular-i18next/compare/v8.1.0-beta...v8.1.0-beta.1) (2020-02-20)
### Features
* improved typings ([214e35d](https://github.com/Romanchuk/angular-i18next/commit/214e35d))
# [8.1.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v7.2.0-beta...v8.1.0-beta) (2020-02-20)
## [8.0.1](https://github.com/Romanchuk/angular-i18next/compare/v8.0.1-beta.0...v8.0.1) (2020-02-18)
## [8.0.1-beta.0](https://github.com/Romanchuk/angular-i18next/compare/v8.0.1-beta...v8.0.1-beta.0) (2020-02-18)
## [8.0.1-beta](https://github.com/Romanchuk/angular-i18next/compare/v8.0.0...v8.0.1-beta) (2020-02-18)
# [8.0.0](https://github.com/Romanchuk/angular-i18next/compare/v8.0.0-beta.1...v8.0.0) (2020-02-14)
# [8.0.0-beta.1](https://github.com/Romanchuk/angular-i18next/compare/v8.0.0-beta...v8.0.0-beta.1) (2020-02-13)
# [8.0.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v7.2.0-beta...v8.0.0-beta) (2020-02-13)
# [7.2.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v7.0.0...v7.2.0-beta) (2020-01-28)
### Bug Fixes
* I18NextEagerPipe ([8dbefe1](https://github.com/Romanchuk/angular-i18next/commit/8dbefe1))
# [7.0.0](https://github.com/Romanchuk/angular-i18next/compare/v6.1.0...v7.0.0) (2019-06-05)
# [6.1.0](https://github.com/Romanchuk/angular-i18next/compare/v6.1.0-beta...v6.1.0) (2019-05-27)
# [6.1.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v6.0.1...v6.1.0-beta) (2019-05-25)
## [6.0.1](https://github.com/Romanchuk/angular-i18next/compare/v6.0.0...v6.0.1) (2019-03-11)
# [6.0.0](https://github.com/Romanchuk/angular-i18next/compare/v6.0.0-beta.0...v6.0.0) (2019-02-10)
# [6.0.0-beta.0](https://github.com/Romanchuk/angular-i18next/compare/v6.0.0-beta...v6.0.0-beta.0) (2019-02-10)
# [6.0.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v5.0.6...v6.0.0-beta) (2019-02-10)
## [5.0.6](https://github.com/Romanchuk/angular-i18next/compare/v5.0.5...v5.0.6) (2018-12-03)
## [5.0.5](https://github.com/Romanchuk/angular-i18next/compare/v5.0.4...v5.0.5) (2018-12-03)
## [5.0.4](https://github.com/Romanchuk/angular-i18next/compare/v5.0.3...v5.0.4) (2018-12-03)
## [5.0.3](https://github.com/Romanchuk/angular-i18next/compare/v5.0.2...v5.0.3) (2018-12-03)
## [5.0.2](https://github.com/Romanchuk/angular-i18next/compare/v5.0.1...v5.0.2) (2018-12-03)
### Bug Fixes
* package.json ([54a8c37](https://github.com/Romanchuk/angular-i18next/commit/54a8c37))
## [5.0.1](https://github.com/Romanchuk/angular-i18next/compare/v5.0.0...v5.0.1) (2018-11-28)
# [5.0.0](https://github.com/Romanchuk/angular-i18next/compare/v5.0.0-beta2...v5.0.0) (2018-11-28)
# [5.0.0-beta2](https://github.com/Romanchuk/angular-i18next/compare/v5.0.0-beta...v5.0.0-beta2) (2018-11-28)
# [5.0.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v4.0.0...v5.0.0-beta) (2018-11-28)
### Bug Fixes
* docs ([220a0b8](https://github.com/Romanchuk/angular-i18next/commit/220a0b8))
# [4.0.0](https://github.com/Romanchuk/angular-i18next/compare/v4.0.0-beta...v4.0.0) (2018-06-25)
In v4 passed through most of i18next api methods
1. Update angular to v6+
2. Update rxjs to v6.2.0+
# [4.0.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v3.4.2...v4.0.0-beta) (2018-06-11)
## [3.4.2](https://github.com/Romanchuk/angular-i18next/compare/v3.4.1...v3.4.2) (2018-05-05)
## [3.4.1](https://github.com/Romanchuk/angular-i18next/compare/v3.4.0...v3.4.1) (2018-04-29)
- default formater fixes
# [3.4.0](https://github.com/Romanchuk/angular-i18next/compare/v3.3.0...v3.4.0) (2018-04-29)
- i18next v11 support
- fix: [format pipe](https://github.com/Romanchuk/angular-i18next/issues/15)
# [3.3.0](https://github.com/Romanchuk/angular-i18next/compare/v3.3.0-beta.2...v3.3.0) (2018-03-12)
- added umd bundle
- comments cleanup
- updated dev dependencies
# [3.3.0-beta.2](https://github.com/Romanchuk/angular-i18next/compare/v3.3.0-beta.1...v3.3.0-beta.2) (2018-03-12)
# [3.3.0-beta.1](https://github.com/Romanchuk/angular-i18next/compare/v3.2.0...v3.3.0-beta.1) (2018-02-04)
# [3.2.0](https://github.com/Romanchuk/angular-i18next/compare/v3.1.1...v3.2.0) (2018-01-17)
### Bug Fixes
* [aot build failed](Romanchuk/angular-i18next#10)
### Breaking changes
Removed parameter 'localizeTitle' from forRoot method.
You need to manually resolve Title as I18NextTitle for same behavior.
## [3.1.1](https://github.com/Romanchuk/angular-i18next/compare/v3.1.0...v3.1.1) (2018-01-01)
### Bug Fixes
* bug namespace fallback ([a16b067](https://github.com/Romanchuk/angular-i18next/commit/a16b067))
* conventional-github-releaser run ([df3bb84](https://github.com/Romanchuk/angular-i18next/commit/df3bb84))
# [3.1.0](https://github.com/Romanchuk/angular-i18next/compare/v3.0.0...v3.1.0) (2017-12-22)
It is possible to pass array of namespaces (or scopes). [Key would fallback](https://www.i18next.com/api.html#t) to next namespace in array if the previous failed to resolve.
`[feature.validators:key, validators:key]`
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: ['feature.validators', 'validators']
}
```
# [3.0.0](https://github.com/Romanchuk/angular-i18next/compare/v3.0.0-alpha.2...v3.0.0) (2017-12-15)
# [3.0.0-alpha.2](https://github.com/Romanchuk/angular-i18next/compare/v3.0.0-alpha...v3.0.0-alpha.2) (2017-12-05)
# [3.0.0-alpha](https://github.com/Romanchuk/angular-i18next/compare/v2.0.0...v3.0.0-alpha) (2017-11-27)
# [2.0.0](https://github.com/Romanchuk/angular-i18next/compare/v2.0.0-beta2...v2.0.0) (2017-11-14)
# [2.0.0-beta2](https://github.com/Romanchuk/angular-i18next/compare/v2.0.0-beta...v2.0.0-beta2) (2017-11-05)
# [2.0.0-beta](https://github.com/Romanchuk/angular-i18next/compare/v1.1.0...v2.0.0-beta) (2017-11-05)
# [1.1.0](https://github.com/Romanchuk/angular-i18next/compare/v1.0.2...v1.1.0) (2017-11-04)
## [1.0.2](https://github.com/Romanchuk/angular-i18next/compare/v1.0.1...v1.0.2) (2017-09-22)
## [1.0.1](https://github.com/Romanchuk/angular-i18next/compare/v1.0.0...v1.0.1) (2017-09-21)
# [1.0.0](https://github.com/Romanchuk/angular-i18next/compare/v0.2.4...v1.0.0) (2017-09-21)
## [0.2.4](https://github.com/Romanchuk/angular-i18next/compare/v0.2.3...v0.2.4) (2017-06-29)
## [0.2.3](https://github.com/Romanchuk/angular-i18next/compare/v0.2.2...v0.2.3) (2017-06-29)
## [0.2.2](https://github.com/Romanchuk/angular-i18next/compare/v0.2.1...v0.2.2) (2017-06-29)
### Bug Fixes
* **I18NextService:** context-safe calls of i18next methods ([455a07d](https://github.com/Romanchuk/angular-i18next/commit/455a07d))
## [0.2.1](https://github.com/Romanchuk/angular-i18next/compare/v0.2.0...v0.2.1) (2017-06-29)
### Bug Fixes
* **package:** return back required exports ([fb7ead6](https://github.com/Romanchuk/angular-i18next/commit/fb7ead6))
# [0.2.0](https://github.com/Romanchuk/angular-i18next/compare/0.1.0...0.2.0) (2017-06-28)
### Features
* **package:** AOT support added ([fc1f66d](https://github.com/Romanchuk/angular-i18next/commit/fc1f66d))
================================================
FILE: libs/angular-i18next/README.md
================================================
[](https://badge.fury.io/js/angular-i18next)
[](https://npmjs.org/package/angular-i18next)
[](https://travis-ci.com/Romanchuk/angular-i18next)
[](https://coveralls.io/github/Romanchuk/angular-i18next?branch=master)
[](http://commitizen.github.io/cz-cli/)
[](https://david-dm.org/Romanchuk/angular-i18next)
[](https://david-dm.org/Romanchuk/angular-i18next?type=dev)
[](https://www.paypal.com/paypalme2/sergeyromanchuk/10USD)
[](https://github.com/romanchuk/angular-i18next)
# angular-i18next
[i18next](http://i18next.com/) v8.4+ integration with [angular](https://angular.io/) v2.0+
[Live DEMO](https://romanchuk.github.io/angular-i18next-demo/)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Cookbook](#cookbook)
- [Deep integration](#deep-integration)
- [In-project testing](#in-project-testing)
- [Demo](#demo)
- [Articles](#articles)
- [Support project](#support-on-beerpay)
# Features
- Native i18next [options](https://www.i18next.com/configuration-options.html)
- Promise initialization
- [i18next plugin](https://www.i18next.com/plugins-and-utils.html#plugins) support
- Events support
- Namespaces lazy load
- i18next native [format](https://www.i18next.com/api.html#format) support
- document.title localization
- Error handling strategies
- i18next namespaces and scopes (prefixes) for angular modules and components
- AOT support
- [Angular Package Format](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview) support
[Related packages](#deep-integration) also has implementations for:
- Reactive forms validators localization
- Http error message localizer
# Cheers!
Hey dude! Help me out for a couple of :beers:!
Поддержи проект - угости автора кружечкой пива!
[](https://www.paypal.com/paypalme2/sergeyromanchuk/10USD)
# Installation
**1.** Install package
```
npm install i18next --save
npm install angular-i18next --save
```
**2.** Import I18NextModule to AppModule
```typescript
import { I18NextModule } from 'angular-i18next';
@NgModule({
bootstrap: [ AppComponent ],
declarations: [
AppComponent
],
import: [
I18NextModule.forRoot()
]
})
export class AppModule {}
```
**3.** Import I18NextModule.forRoot() to AppModule and setup provider with "init" method (use native [options](https://www.i18next.com/configuration-options.html)). Angular would not load until i18next initialize event fired
```typescript
export function appInit(i18next: ITranslationService) {
return () => i18next.init({
whitelist: ['en', 'ru'],
fallbackLng: 'en',
debug: true,
returnEmptyString: false,
ns: [
'translation',
'validation',
'error'
],
});
}
export function localeIdFactory(i18next: ITranslationService) {
return i18next.language;
}
export const I18N_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [I18NEXT_SERVICE],
multi: true
},
{
provide: LOCALE_ID,
deps: [I18NEXT_SERVICE],
useFactory: localeIdFactory
}];
```
```typescript
@NgModule({
imports: [
...
I18NextModule.forRoot()
],
providers: [
...
I18N_PROVIDERS,
],
bootstrap: [AppComponent]
})
export class AppModule {
}
```
# Usage
### Pipes
Use "i18next" pipe to translate key:
Trigger native i18next [format method](https://www.i18next.com/formatting.html) by using I18NextFormatPipe or I18NextPipe with option 'format':
`{{ 'any_key' | i18next | i18nextFormat }}`
`{{ 'any_key' | i18next: { format: 'cap' } }}`
`{{ 'any_key' | i18nextCap }}`
**Note:** Using "i18nextCap" you will get the same result as `i18next: { format: 'cap' }`
**REMEMBER** that format will not work until you set "interpolation.format" function in i18next options.
I18NextModule has static method `static interpolationFormat(customFormat: Function = null): Function` that can be used as default interpolation format function (it provides 'upper', 'cap' and 'lower' formatters). You also can pass your custom function to be called after I18NextModule formatters:
```typescript
const i18nextOptions = {
whitelist: ['en', 'ru'],
ns: [
'translation',
'validation',
'error',
],
interpolation: {
format: I18NextModule.interpolationFormat((value, format, lng) => {
if(value instanceof Date)
return moment(value).format(format);
return value;
});
// format: I18NextModule.interpolationFormat()
}
};
```
**i18nextEager pipe**
This is the impure analog of *i18next pipe* that is subscribed to language change, it will change string right away to choosen language (without reloading page).
**Warning!**: Use i18nextEager only in combine with [OnPush change detection strategy](https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4), or else (default change detection) each pipe will retrigger more than one time (cause of performance issues).
Subscribing to event observables:
```typescript
this.i18NextService.events.languageChanged.subscribe(lang => {
// do something
})
```
Add a provider to module/component if you want to prefix child i18next keys:
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: 'feature' // set 'feature:' prefix
}
```
```typescript
{
provide: I18NEXT_SCOPE,
useValue: 'person' // set 'person.' prefix
}
```
Since v3.1.0+ it is possible to pass array of namespaces (or scopes). [Key would fallback](https://www.i18next.com/api.html#t) to next namespace in array if the previous failed to resolve.
`[feature_validators:key, validators:key]`
```typescript
{
provide: I18NEXT_NAMESPACE,
useValue: ['feature_validators', 'validators']
}
```
_NOTE:_ **Do NOT** use default (or custom) i18next delimiters in namespace names.
### Document title
If you want to turn on document title localization resolve Title as `I18NextTitle` imported from 'angular-i18next':
```typescript
{
provide: Title,
useClass: I18NextTitle
}
```
Also you can implement your own Title service with specific behavior. Inject `I18NextPipe` (or `I18NextService`) to service/component:
```typescript
import { Injectable, Inject } from '@angular/core';
import { Title, DOCUMENT } from '@angular/platform-browser';
import { I18NextPipe } from 'angular-i18next';
@Injectable()
export class I18NextTitle extends Title {
constructor(private i18nextPipe: I18NextPipe, @Inject(DOCUMENT) doc) {
super(doc);
}
setTitle(value: string) {
return super.setTitle(this.translate(value));
}
private translate(text: string) {
return this.i18nextPipe.transform(text, { format: 'cap'});
}
}
```
Ways to use I18NextService in your code:
> **Warning:** Injection of **I18NextService** is possible, but it would not consider I18NEXT_NAMESPACE and I18NEXT_SCOPE providers. There are 2 possible reasons to inject **I18NextService**: initialization and subscription to its events. In all other cases inject **I18NextPipe**.
1) **Recommended way:** Inject via **I18NEXT_SERVICE** token. By default it will inject instance of **I18NextService**.
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService)
```
2) Legacy way: Inject via type
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
private i18NextService: I18NextService)
```
### Error handling
Error handling is now configurable:
1) By default i18next promise will use NativeErrorHandlingStrategy. I18Next would be always resolve succesfully. Error could be get from 'then' handler parameter.
2) Set StrictErrorHandlingStrategy to reject load promises (init, languageChange, loadNamespaces) on first load fail (this was default in v2 but changed to fit [native i18next behavior](https://github.com/Romanchuk/angular-i18next/issues/9):
`I18NextModule.forRoot({ errorHandlingStrategy: StrictErrorHandlingStrategy })`
### Lazy loading
Use I18NEXT_NAMESPACE_RESOLVER in your routes to to load i18next namespace.
Note: It is not neccesary to register lazy loading namespaces in global i18next options.
```
{
path: 'rich_form',
loadChildren: 'app/features/rich_form_feature/RichFormFeatureModule#RichFormFeatureModule',
data: {
i18nextNamespaces: ['feature.rich_form']
},
resolve: {
i18next: I18NEXT_NAMESPACE_RESOLVER
}
},
```
Use I18NextService.loadNamespaces() method to load namespaces in code.
# Cookbook
### i18next plugin support
```typescript
import { I18NextModule, ITranslationService, I18NEXT_SERVICE } from 'angular-i18next';
// import Backend from 'i18next-xhr-backend'; //for i18next < 20.0.0
import HttpApi from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
...
i18next.use(HttpApi)
.use(LanguageDetector)
.init(i18nextOptions)
```
### Initialize i18next before angular application
Angular would not load until i18next initialize event fired
```typescript
export function appInit(i18next: ITranslationService) {
return () => i18next.init();
}
export function localeIdFactory(i18next: ITranslationService) {
return i18next.language;
}
export const I18N_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: appInit,
deps: [I18NEXT_SERVICE],
multi: true
},
{
provide: LOCALE_ID,
deps: [I18NEXT_SERVICE],
useFactory: localeIdFactory
}];
```
### Document title update on language or route change
```typescript
export class AppComponent implements OnInit {
constructor(private router: Router,
private title: Title,
@Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService) {
// page title subscription
// https://toddmotto.com/dynamic-page-titles-angular-2-router-events#final-code
this.router.events
.filter(event => event instanceof NavigationEnd)
.map(() => this.router.routerState.root)
.map(route => {
while (route.firstChild) route = route.firstChild;
return route;
})
.filter(route => route.outlet === 'primary')
.mergeMap(route => route.data)
.subscribe((event) => this.updatePageTitle(event['title']));
}
ngOnInit() {
this.i18NextService.events.languageChanged.subscribe(lang => {
let root = this.router.routerState.root;
if (root != null && root.firstChild != null) {
let data: any = root.firstChild.data;
this.updatePageTitle(data && data.value && data.value.title);
}
});
}
updatePageTitle(title: string): void {
let newTitle = title || 'application_title';
this.title.setTitle(newTitle);
}
}
```
Routes example:
```typescript
const appRoutes: Routes = [
{
path: 'error',
component: AppErrorComponent,
data: { title: 'error:error_occured' }
},
{
path: 'denied',
component: AccessDeniedComponent,
data: { title: 'error:access_denied' }
}
];
```
# New angular version released, but angular-i18next is not released YET!!!
Angular releases mostly don't break angular-i18next, but we cannot tell ahead that current version of `angular-i18next` will work correctly with latest angular version.
You can override an angular-i18next `peerDependencies` in your `package.json` on your **own risk**:
```json
"overrides": {
"angular-i18next": {
"@angular/common": "*",
"@angular/core": "*",
"@angular/platform-browser": "*"
}
}
```
# Deep integration
List of packages to integrate angular and i18next more deeply:
- [angular-validation-message](https://github.com/Romanchuk/angular-validation-message) - angular [reactive form validators](https://angular.io/guide/reactive-forms#step-2-making-a-field-required) integration (and [angular-validation-message-i18next ](https://github.com/Romanchuk/angular-validation-message-i18next) is i18next bridge to it). It gives you possibility to localize form validators and it automatically puts localized validator error message to markup (if there is one).
- [angular-i18next-error-interceptor](https://github.com/LCGroupIT/angular-i18next-error-interceptor) - allows you to set default errot messages for non-200 http status responses. So if the back-end didn't specify { message: 'some error' } in a response (sort of contract with our backend) interceptor will check response status code and will fill { message: 'Server is not available. Please try again.' }. Also package includes pipe where you can pass HttpErrorResponse and it will return error message whenever it's back-end message or our localized message.
# In-project testing
You might want to unit-test project components that are using i18next pipes
Example tests setup:
[/tests/projectTests/projectTests.spec.ts](https://github.com/Romanchuk/angular-i18next/blob/master/tests/projectTests/projectTests.spec.ts)
# Demo
[Live DEMO](https://romanchuk.github.io/angular-i18next-demo/)
Demo app source code available here: https://github.com/Romanchuk/angular-i18next-demo
# Articles
- [Angular L10n with I18next](https://phrase.com/blog/posts/angular-l10n-with-i18next/)
- [Best Libraries for Angular I18n](https://phrase.com/blog/posts/best-libraries-for-angular-i18n/)
================================================
FILE: libs/angular-i18next/forms/ng-package.json
================================================
{}
================================================
FILE: libs/angular-i18next/forms/src/components/validation-message.component.ts
================================================
import { Component, ViewEncapsulation, computed, effect, inject, input, signal } from "@angular/core";
import { AbstractControl, NgControl } from "@angular/forms";
import { I18NEXT_NAMESPACE, I18NextCapPipe } from "angular-i18next";
import { ValidationMessage } from "../models";
import { combineLatest, startWith, Subscription, tap } from "rxjs";
@Component({
selector: 'i18next-validation-message',
template: `