Skip to content

Commit

Permalink
Feature: Support internationalization (#1500)
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheeguerin authored Jul 26, 2018
1 parent c5b5a08 commit d42ff2a
Show file tree
Hide file tree
Showing 67 changed files with 1,244 additions and 479 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ release/
.awcache
coverage/
*.stackdump

# Ignore the english compiled file
i18n/resources.en.json
i18n/xliff/
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"editor.tabSize": 4,
// When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents.
"editor.detectIndentation": false,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.tsdk": "node_modules/typescript/lib",
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
Expand Down
4 changes: 3 additions & 1 deletion app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { BatchExplorerErrorHandler } from "app/error-handler";

// services
import { HttpModule } from "@angular/http";
import { MaterialModule } from "@batch-flask/core";
import { MaterialModule, TranslationsLoaderService } from "@batch-flask/core";
import { CommonModule } from "app/components/common";
import { LayoutModule } from "app/components/layout";
import { MiscModule } from "app/components/misc";
Expand All @@ -48,6 +48,7 @@ import {
AdalService,
AppInsightsApiService,
AppInsightsQueryService,
AppTranslationsLoaderService,
ApplicationService,
ArmHttpService,
AuthorizationHttpService,
Expand Down Expand Up @@ -126,6 +127,7 @@ const graphApiServices = [AADApplicationService, AADGraphHttpService, MsGraphHtt
],
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy },
{ provide: TranslationsLoaderService, useClass: AppTranslationsLoaderService },
AccountService,
AdalService,
AppInsightsApiService,
Expand Down
26 changes: 15 additions & 11 deletions app/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { ipcRenderer } from "electron";
import { ipcRenderer, remote } from "electron";

import "@batch-flask/extensions";

Expand All @@ -21,16 +21,20 @@ import "./styles/main.scss";

ipcRenderer.send("initializing");

platformBrowserDynamic().bootstrapModule(AppModule)
.then(() => {
// console.timeEnd("Bootstrap");
// console.time("Render");
// console.profile("Render profile");
})
.catch(error => {
log.error("Bootstrapping failed :: ", error);
handleCoreError(error);
});
Promise.resolve().then(() => {
if (process.env.NODE_ENV !== "production") {
return (remote.getCurrentWindow() as any).translationsLoader.load();
}
}).then(() => {
return platformBrowserDynamic().bootstrapModule(AppModule);
}).then(() => {
// console.timeEnd("Bootstrap");
// console.time("Render");
// console.profile("Render profile");
}).catch(error => {
log.error("Bootstrapping failed :: ", error);
handleCoreError(error);
});

document.addEventListener("dragover", (event) => {
event.dataTransfer.dropEffect = "none";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy {

private _getTreeRowsForNode(node: FileTreeNode, indent = 0): TreeRow[] {
const rows = [];
for (const [_, child] of node.children) {
for (const [, child] of node.children) {
if (this.autoExpand && !(child.path in this.expandedDirs)) {
this.expandedDirs.add(child.path);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from "@angular/core";
import { I18nService } from "@batch-flask/core";
import { AccountService } from "app/services";

import { Subscription } from "rxjs";
import "./main-navigation.scss";

Expand All @@ -16,7 +16,7 @@ export class MainNavigationComponent implements OnDestroy {

private _accountSub: Subscription;

constructor(accountService: AccountService, private changeDetector: ChangeDetectorRef) {
constructor(accountService: AccountService, private changeDetector: ChangeDetectorRef, i18n: I18nService) {
this._accountSub = accountService.currentAccountId.subscribe((accountId) => {
this.selectedId = accountId;
this.changeDetector.markForCheck();
Expand Down
16 changes: 8 additions & 8 deletions app/components/layout/main-navigation/main-navigation.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
<ul class="top">
<bl-clickable routerLink="/accounts" routerLinkActive="active" title="Dashboard">
<i class="fa fa-th fa-lg"></i><div class="label">Dash</div>
<i class="fa fa-th fa-lg"></i><div class="label">{{'main-navigation.dashboard' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/jobs" routerLinkActive="active">
<i class="fa fa-tasks fa-lg"></i><div class="label">Jobs</div>
<i class="fa fa-tasks fa-lg"></i><div class="label">{{'main-navigation.jobs' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/jobschedules" routerLinkActive="active" title="Job schedules">
<i class="fa fa-calendar fa-lg"></i><div class="label">Schedules</div>
<i class="fa fa-calendar fa-lg"></i><div class="label">{{'main-navigation.jobschedules' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/pools" routerLinkActive="active">
<i class="fa fa-database fa-lg"></i><div class="label">Pools</div>
<i class="fa fa-database fa-lg"></i><div class="label">{{'main-navigation.pools' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/applications" routerLinkActive="active" title="Application packages">
<i class="fa fa-file-archive-o fa-lg"></i><div class="label">Packages</div>
<i class="fa fa-file-archive-o fa-lg"></i><div class="label">{{'main-navigation.packages' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/certificates" routerLinkActive="active" title="Certificates">
<i class="fa fa-certificate fa-lg"></i><div class="label">Certificate</div>
<i class="fa fa-certificate fa-lg"></i><div class="label">{{'main-navigation.certificates' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount routerLink="/data" routerLinkActive="active" title="File groups">
<i class="fa fa-cloud-upload fa-lg"></i><div class="label">Data</div>
<i class="fa fa-cloud-upload fa-lg"></i><div class="label">{{'main-navigation.data' | i18n}}</div>
</bl-clickable>
<bl-clickable *blHiddenIfNoAccount href="#" routerLink="market" routerLinkActive="active" title="Gallery">
<i class="fa fa-cubes" aria-hidden="true"></i><div class="label">Gallery</div>
<i class="fa fa-cubes" aria-hidden="true"></i><div class="label">{{'main-navigation.gallery' | i18n}}</div>
</bl-clickable>
</ul>
<div class="flex-separator"></div>
Expand Down
10 changes: 10 additions & 0 deletions app/components/layout/main-navigation/main-navigation.i18n.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
main-navigation:
dashboard: Dash
jobs: Jobs
jobschedules: Job schedules
pools: Pools
packages: Packages
certificates: Certificates
data: Data
gallery: Gallery
profile: Profile
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";
import { MaterialModule } from "@batch-flask/core";
import { SelectModule } from "@batch-flask/ui";
import { I18nUIModule, SelectModule } from "@batch-flask/ui";
import { PermissionService } from "@batch-flask/ui/permission";
import * as moment from "moment";
import { of } from "rxjs";

import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { I18nTestingModule } from "@batch-flask/core/testing";
import { DialogService } from "@batch-flask/ui/dialogs";
import { DurationPickerComponent } from "@batch-flask/ui/duration-picker";
import { NodeUserCredentialsFormComponent } from "app/components/node/connect";
Expand Down Expand Up @@ -46,7 +47,10 @@ describe("NodeUserCredentialsForm", () => {
keys: of([]),
};
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, MaterialModule, NoopAnimationsModule, SelectModule],
imports: [
FormsModule, ReactiveFormsModule, MaterialModule,
NoopAnimationsModule, SelectModule, I18nTestingModule, I18nUIModule,
],
declarations: [
NodeUserCredentialsFormComponent, TestComponent, SimpleFormMockComponent,
SSHKeyPickerComponent, DurationPickerComponent,
Expand Down
14 changes: 14 additions & 0 deletions app/services/app-translation-loader.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable } from "@angular/core";
import { TranslationsLoaderService } from "@batch-flask/core";
import { remote } from "electron";

@Injectable()
export class AppTranslationsLoaderService extends TranslationsLoaderService {
public translations = new Map<string, string>();

constructor() {
super();
const translationsLoader = (remote.getCurrentWindow() as any).translationsLoader;
this.translations = new Map(JSON.parse(translationsLoader.serializedTranslations));
}
}
3 changes: 2 additions & 1 deletion app/services/batch-labs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class BatchExplorerService {
constructor(private remote: ElectronRemote) {
this._app = remote.getCurrentWindow().batchExplorerApp;
this._app.azureEnvironmentObs.subscribe((x) => {
this._azureEnvironment = x;
// Clone the environement to prevent calling the electron ipc sync for every key
this._azureEnvironment = new AzureEnvironment(x);
});
this.autoUpdater = this._app.autoUpdater;
this.aadService = this._app.aadService;
Expand Down
1 change: 1 addition & 0 deletions app/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./azure-batch";
export * from "./account.service";
export * from "./app-translation-loader.service";
export * from "./authorization-http";
export * from "./application.service";
export * from "./app-insights";
Expand Down
6 changes: 3 additions & 3 deletions app/services/storage/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./blobStorageResult";
export * from "./sharedAccessPolicy";
export * from "./storageRequestOptions";
export * from "./blob-storage-result";
export * from "./shared-access-policy";
export * from "./storage-request-options";
2 changes: 1 addition & 1 deletion app/services/themes/theme.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class ThemeService implements OnDestroy {
(window as any).setTheme = (val) => {
this.setTheme(val);
};

this.currentTheme = this._currentTheme.filter(x => x !== null);
this._subs.push(this.currentTheme.subscribe((theme) => {
this._applyTheme(theme);
Expand All @@ -55,7 +56,6 @@ export class ThemeService implements OnDestroy {

public async init() {
this._baseThemeDefinition = await this._loadTheme(this.baseTheme);
await this.setTheme(this.defaultTheme);
}

public ngOnDestroy() {
Expand Down
2 changes: 1 addition & 1 deletion config/webpack.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const baseConfig = {

resolve: {
extensions: [".ts", ".js", ".json", ".scss", ".css", ".html"],
modules: [helpers.root(), helpers.root("src"), helpers.root("node_modules")],
modules: [helpers.root(), helpers.root("src"), "node_modules"],
},

module: {
Expand Down
32 changes: 32 additions & 0 deletions docs/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Localization in Batch Explorer

To make component localizable you can use one of the following
* `i18n` pipe
* `I18nService`

## Define key and language
The translations will be automatically read from files with the `i18n.yml` extension. e.g. `[filename].i18n.yml`
You'll need to restart the app if you add a new file. Just refreshing the page will update the translations.
Create a file with the name `[my-component].i18n.yml` next to the `[my-component].component.ts` and other files.

## Usage
### `i18n` Pipe
```html
<div>
{{'my-key.my-sub-key' | i18n}}
</div>
```


### I18nService
```ts
class MyComponent {
constructor(private i18n: I18nService){

}

public get title() {
return this.i18n.t("my-key.sub.key", {param: "value"});
}
}
```
1 change: 1 addition & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* [Open a dialog](dialog.md)
* [Store user data/Cache data](store-user-data.md)
* [Use virtual scroll](virtual-scroll.md)
* [Localize](Localization.md)


# Repo management
Expand Down
1 change: 1 addition & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extraResources:
- "**/*"
- "ThirdPartyNotices.txt"
- "data"
- "i18n"
- "node_modules/jschardet/"

protocols:
Expand Down
5 changes: 5 additions & 0 deletions i18n/resources.fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"main-navigation.jobs": "Travaux",
"main-navigation.pools": "Pools",
"main-navigation.jobschedules": "Planifications"
}
Loading

0 comments on commit d42ff2a

Please sign in to comment.