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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 20
- run: npm install
- run: npm run bootstrap
- run: npm run lint
Expand All @@ -24,10 +24,10 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm install
- run: npm run bootstrap
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
6 changes: 5 additions & 1 deletion packages/angular/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "projects/tx-native-angular-sdk/tsconfig.spec.json",
"karmaConfig": "projects/tx-native-angular-sdk/karma.conf.js",
"codeCoverage": true
"codeCoverage": true,
"watch": false
}
},
"lint": {
Expand All @@ -53,5 +54,8 @@
"@angular-eslint/schematics:library": {
"setParserOptionsProject": true
}
},
"cli": {
"analytics": false
}
}
61 changes: 32 additions & 29 deletions packages/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,39 @@
"test": "ng test"
},
"private": true,
"engines": {
"node": ">=18.19.0"
},
"dependencies": {
"@angular/animations": "~16.2.7",
"@angular/common": "~16.2.7",
"@angular/compiler": "~16.2.7",
"@angular/core": "~16.2.7",
"@angular/forms": "~16.2.7",
"@angular/platform-browser": "^16.2.7",
"@angular/platform-browser-dynamic": "^16.2.7",
"@angular/router": "~16.2.7",
"@transifex/native": "^7.1.6",
"rxjs": "~6.6.0",
"@angular/animations": "~19.2.19",
"@angular/common": "~19.2.19",
"@angular/compiler": "~19.2.19",
"@angular/core": "~19.2.19",
"@angular/forms": "~19.2.19",
"@angular/platform-browser": "~19.2.19",
"@angular/platform-browser-dynamic": "~19.2.19",
"@angular/router": "~19.2.19",
"@transifex/native": "^7.1.5",
"rxjs": "~7.8.0",
"tslib": "^2.6.2",
"zone.js": "^0.13.3"
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/architect": "^0.1602.4",
"@angular-devkit/build-angular": "^16.2.4",
"@angular-eslint/builder": "^16.2.0",
"@angular-eslint/eslint-plugin": "^16.2.0",
"@angular-eslint/eslint-plugin-template": "^16.2.0",
"@angular-eslint/schematics": "16.2.0",
"@angular-eslint/template-parser": "^16.2.0",
"@angular/cli": "^16.2.4",
"@angular/compiler-cli": "^16.2.7",
"@types/jasmine": "~4.0.0",
"@types/node": "^16.18.70",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@angular-devkit/architect": "0.1902.19",
"@angular-devkit/build-angular": "~19.2.19",
"@angular-eslint/builder": "~19.2.0",
"@angular-eslint/eslint-plugin": "~19.2.0",
"@angular-eslint/eslint-plugin-template": "~19.2.0",
"@angular-eslint/schematics": "~19.2.0",
"@angular-eslint/template-parser": "~19.2.0",
"@angular/cli": "~19.2.0",
"@angular/compiler-cli": "~19.2.19",
"@types/jasmine": "~5.1.0",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-airbnb-typescript": "^16.2.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prefer-arrow": "^1.2.3",
Expand All @@ -69,15 +72,15 @@
"eslint-plugin-rxjs-angular": "^2.0.1",
"eslint-plugin-tsdoc": "^0.2.17",
"eslint-plugin-unicorn": "^46.0.1",
"jasmine-core": "~4.3.0",
"jasmine-core": "~5.1.0",
"jasmine-spec-reporter": "^7.0.0",
"karma": "~6.4.1",
"karma-chrome-launcher": "~3.1.1",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.1.0",
"ng-packagr": "^16.2.3",
"ts-node": "~8.3.0",
"typescript": "~4.9.5"
"ng-packagr": "~19.2.0",
"ts-node": "~10.9.0",
"typescript": "~5.5.0"
}
}
135 changes: 122 additions & 13 deletions packages/angular/projects/tx-native-angular-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ If you are upgrading from the `1.x.x` version, please read this [migration guide
* [translate Pipe](#translate-pipe)
* [Language Picker Component](#language-picker-component)
* [TX Instance Component](#tx-instance-component)
* [TxInstanceContext](#txinstancecontext)
* [txLoadTranslations Directive](#txloadtranslations-directive)
* [License](#license)


# Requirements

Angular 16 is required. If you are using Angular 14 or 15, please use the `6.x.x` version of
Transifex Native related packages. If you are using Angular 12 or 13, please use the `5.x.x` version of
Transifex Native related packages. If you are using Angular 11, please use the `1.x.x` version of
Transifex Native related packages. Other Angular versions are not officially supported at the moment.
Angular 19 and Node.js >= 18.19 are required. If you are using Angular 16, please use the `7.1.x` version of
Transifex Native related packages. If you are using Angular 14 or 15, please use the `6.x.x` version.
If you are using Angular 12 or 13, please use the `5.x.x` version.
If you are using Angular 11, please use the `1.x.x` version.
Other Angular versions are not officially supported at the moment.

# Installation

Expand All @@ -85,9 +87,58 @@ npm install @transifex/native @transifex/angular --save
## Initialization

In order to use the TX Native object globally, it is necessary to initialize
the library in the angular application bootstrap, in two locations:
the library in the Angular application bootstrap.

- NgModule initialization
All components, directives, and pipes in this SDK are **standalone**, so they can be imported directly where needed. A backward-compatible `TxNativeModule` is still provided for NgModule-based applications.

**`TranslationService` and the `@T` decorator:** `@T` uses the same root `TranslationService` instance as Angular DI (including any extra instances registered for `<tx-instance>`). The property is translated **when it is read** (lazy), not when the class is loaded. `TxNativeModule.forRoot()` registers `provideTxNativeEagerTranslationService()` so that service exists during app startup. If you use **`bootstrapApplication`** (no `forRoot()`), add `provideTxNativeEagerTranslationService()` to your root `providers` whenever you use `@T`, so the root service is created before the first `@T` access—especially if your first screen might not inject `TranslationService` yet.

### Standalone applications

Import the components you need directly in your standalone components or in the `imports` array of your app configuration.

If you use the **`@T` property decorator**, register the eager provider in your application root (typically `main.ts`):

```typescript
import { bootstrapApplication } from '@angular/platform-browser';
import { provideTxNativeEagerTranslationService } from '@transifex/angular';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
providers: [provideTxNativeEagerTranslationService()],
}).catch((err) => console.error(err));
```

Example root component:

```typescript
import { Component } from '@angular/core';
import { TranslationService, TComponent, LanguagePickerComponent } from '@transifex/angular';

@Component({
standalone: true,
selector: 'app-root',
imports: [TComponent, LanguagePickerComponent],
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(private translationService: TranslationService) {
translationService.init({
token: '----- here your TX Native token ------',
});
}

async ngOnInit() {
await this.translationService.getLanguages();
await this.translationService.setCurrentLocale('el');
}
}
```

### NgModule

If your application uses NgModules, import `TxNativeModule.forRoot()` in your root module:

```typescript
@NgModule({
Expand All @@ -105,13 +156,12 @@ the library in the angular application bootstrap, in two locations:
// TX Native module declaration
TxNativeModule.forRoot(),
],
providers: [,
],
providers: [],
bootstrap: [AppComponent]
})
```

- Application Boostrap
Then initialize the SDK in your root component:

```typescript
import { Component } from '@angular/core';
Expand All @@ -124,7 +174,6 @@ import { TranslationService } from '@transifex/angular';
})
export class AppComponent {
constructor(private translationService: TranslationService) {
// TX Native library intialization
translationService.init({
token: '----- here your TX Native token ------',
});
Expand Down Expand Up @@ -340,6 +389,8 @@ export interface ITranslateParams {
This is a decorator for using inside classes and components in order to have
properties with the translation and used them in code and templates.

Use **`TxNativeModule.forRoot()`** or **`provideTxNativeEagerTranslationService()`** in standalone apps (see [Initialization](#initialization)) so the root `TranslationService` is available the first time an `@T` property is read. You do not need a separate `TranslationService` instance for the decorator; it shares the one from DI.

An example of use is the following:

```typescript
Expand Down Expand Up @@ -477,12 +528,13 @@ such as `getLanguages`.

## TX Instance Component

Creates a new TX Native instance with the given configuration and adds it to the TX Native main instance. All the nested components will use the new instance in order to fetch the translations. This apply to components:
Creates a new TX Native instance with the given configuration and adds it to the TX Native main instance. All the nested components will use the new instance in order to fetch the translations. This applies to components:

- T/UT
- translate pipe
- Language Picker

Uses `Translation Service` internally to add the instance.
Uses `Translation Service` internally to add the instance and provides a scoped `TxInstanceContext` so that nested components automatically resolve the correct instance. See the [TxInstanceContext](#txinstancecontext) section for details on how this works.

The html selector is `tx-instance`.

Expand Down Expand Up @@ -511,7 +563,7 @@ Accepts properties:

- `token`: The token for the new instance.

- `alias`: A string indetifier of the instance, should be unique. If the identifier already exists, the existing instance with the given alias is used, and no new instance is created.
- `alias`: A string identifier of the instance, should be unique. If the identifier already exists, the existing instance with the given alias is used, and no new instance is created.

- `controlled`: If the new instance is controlled (locale) by the main TX Native instance.

Expand All @@ -523,6 +575,63 @@ Exposes:

- `instanceIsReady`: observable for listening the readiness of the new instance.

## `TxInstanceContext`

`TxInstanceContext` is an injectable service that acts as a lightweight handle for the active TX Native instance alias. It is the mechanism that allows standalone components (`T`, `UT`, `LanguagePickerComponent`, `TranslatePipe`) to know which instance they should use for translations, without requiring a direct reference to the `TXInstanceComponent`.

### How it works

A root-level `TxInstanceContext` is provided application-wide (via `providedIn: 'root'`). Its `alias` defaults to an empty string, which tells components to use the main TX Native instance.

When you wrap components inside a `<tx-instance>` element, that component provides its own `TxInstanceContext` at the component level. Angular's dependency injection will resolve the closest `TxInstanceContext` in the injector hierarchy, so nested components automatically see the alias of the wrapping instance.

```
AppModule / root injector
└─ TxInstanceContext (alias = '', main instance)
├─ <T str="Uses main instance">
└─ <tx-instance alias="homepage" ...>
└─ TxInstanceContext (alias = 'homepage', scoped)
├─ <T str="Uses homepage instance">
└─ {{ 'A string' | translate }}
```

### Using TxInstanceContext directly

In most cases you do not need to interact with `TxInstanceContext` yourself — wrapping components in `<tx-instance>` handles everything automatically. However, if you need to read or react to instance readiness programmatically, you can inject it:

```typescript
import { Component } from '@angular/core';
import { TxInstanceContext, TranslationService } from '@transifex/angular';

@Component({
standalone: true,
selector: 'my-custom-component',
template: `...`
})
export class MyCustomComponent {
constructor(
private translationService: TranslationService,
private txContext: TxInstanceContext,
) {}

getTranslation(str: string): string {
const instance = this.translationService.getInstance(this.txContext.alias);
return instance.translate(str);
}
}
```

### API

| Property / Method | Type | Description |
|-------------------------|--------------------------|-------------------------------------------------------------------------------------------------------|
| `alias` | `string` | The alias of the active TX Native instance. Empty string means the main instance. |
| `instanceIsReady` | `Observable<boolean>` | Emits when the associated instance has finished initializing and fetching translations. |
| `notifyInstanceReady()` | `(ready: boolean) => void` | Called internally by `TXInstanceComponent` to signal readiness. Not typically called by application code. |

## `txLoadTranslations` Directive

This directive can be used within any html or angular tag in order to force a group of translations to be fetched, using a list of tags to retrieve the translations that match.
Expand Down
11 changes: 6 additions & 5 deletions packages/angular/projects/tx-native-angular-sdk/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = function (config) {
jasmine: {
failSpecWithNoExpectations: true,
},
clearContext: false
clearContext: true, // Suppresses "full page reload" false positive in Chrome 128+
},
jasmineHtmlReporter: {
suppressAll: true
Expand All @@ -30,7 +30,7 @@ module.exports = function (config) {
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
reporters: ['progress', 'coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
Expand All @@ -44,10 +44,11 @@ module.exports = function (config) {
},
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox'],
flags: ['--no-sandbox', '--headless=old'],
},
},
singleRun: true, // Moved this property outside the customLaunchers object
restartOnFileChange: true, // Moved this property outside the customLaunchers object
singleRun: true,
restartOnFileChange: false,
browserDisconnectTolerance: 2,
});
};
4 changes: 2 additions & 2 deletions packages/angular/projects/tx-native-angular-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"repository": "git://github.com/transifex/transifex-javascript.git",
"license": "Apache-2.0",
"peerDependencies": {
"@angular/common": "^16.0.0",
"@angular/core": " ^16.0.0",
"@angular/common": "^19.0.0",
"@angular/core": "^19.0.0",
"@transifex/native": "^7.0.1"
}
}
Loading
Loading