Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3dfeb0f
Added fake localizer
dpwatrous Apr 19, 2023
a40d8b2
Switch desktop app to use new translations
skapur12 Apr 24, 2023
ab48b34
Add script to combine translated files by language
skapur12 Apr 24, 2023
8bff893
Add global build-translations command
skapur12 Apr 24, 2023
ac32380
Update comments and docs accordingly with changes
skapur12 Apr 24, 2023
7da21d9
Run merge-translations script on Azure DevOps path
skapur12 Apr 24, 2023
4609a68
Add localization for Batch Explorer web version
skapur12 Apr 26, 2023
9935ea7
Generate translations for Create Account buttons
skapur12 Apr 26, 2023
5488d42
Add basic i18n support for web package
skapur12 Apr 28, 2023
af4d629
Rename StandardLocalizer to BrowserLocalizer
skapur12 May 9, 2023
cb3f6e1
Add merge translations script for web and desktop
skapur12 May 9, 2023
07b7e09
Remove powershell merge translations script
skapur12 May 9, 2023
dbf75a1
Add localization support for desktop app
skapur12 May 9, 2023
8af9a55
Copy translations to web too and not just desktop
skapur12 May 9, 2023
20920ca
Add translations for playground buttons
skapur12 May 9, 2023
2ca588d
Gitignore generated localization files
skapur12 May 9, 2023
71a169c
Update localization docs with setup instructions
skapur12 May 9, 2023
2abf553
Add package translations to mergeTranslations
skapur12 May 10, 2023
33848fd
Optimize package file creation and fix unit test
skapur12 May 10, 2023
fe969ad
Update Electron app localization unit tests
skapur12 May 10, 2023
9530375
Address all feedback on PR
skapur12 Jun 20, 2023
eb1eb82
Fix desktop localizer, translation function, tests
skapur12 Jun 30, 2023
47e9245
Fix Prettier issue with account yml file
skapur12 Jun 30, 2023
92134c0
Update client translations unit test accordingly
skapur12 Jun 30, 2023
7972190
Add http-localizer unit test and minor fixes
skapur12 Jul 27, 2023
0307daa
Remove CustomGlobal and simplify navigator object
skapur12 Jul 28, 2023
cc8f35d
Add getLocale function to each localizer
skapur12 Jul 31, 2023
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Localize/out/
/packages/playground/resources/i18n/*
/packages/react/resources/i18n/*
/packages/service/resources/i18n/*
/util/bux/resources/i18n/json
web/dev-server/resources/i18n/*
desktop/resources/i18n/*
2 changes: 1 addition & 1 deletion .vsts/linux/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ steps:
echo "No changes. Nothing to do"
exit 0
else
echo "Text changes detected: $changed. Pipeline failed."
echo "Text changes detected: $changed. Pipeline failed. Please run 'npm run build' on your local machine and push the generated files."
exit 1
fi
workingDirectory: desktop
Expand Down
3 changes: 3 additions & 0 deletions Localize/LocProject.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
},
{
"SourceFile": "desktop\\i18n\\resources.resjson"
},
{
"SourceFile": "web\\i18n\\resources.resjson"
}
]
}
Expand Down
90 changes: 42 additions & 48 deletions Localize/copy-translations.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ if ($artifactsPath -eq "") {
$sourceRoot = $artifactsPath
}

$packageNames = @("common", "service", "playground", "react")
$packageNames = @("common", "service", "playground", "react", "web", "desktop")

# Get language directories
$languageDirs = Get-ChildItem -Path $sourceRoot -Directory

# If this script is run locally, in addition to the resjson files, it adds a json directory containing json files for local development
# If script is run on ADO, it only adds the resjson files used in production

# Strip out resjson comments and resjson-specific formatting before writing the result to json file
function Convert-ResjsonToJson {
param (
Expand All @@ -43,63 +40,60 @@ function Convert-ResjsonToJson {
Set-Content -Path $targetPath -Value $cleanedJsonContent
}

Write-Host "Copying translation files"

foreach ($languageDir in $languageDirs) {
$languageId = $languageDir.Name
Write-Verbose "Processing language: $languageId"
function Copy-Resources {
param (
[string]$packageName,
[string]$languageDirFullName,
[string]$languageId
)

# Process package directories
foreach ($packageName in $packageNames) {
$sourcePath = Join-Path $($languageDir.FullName) "packages/$packageName/i18n/resources.resjson"
$targetDir = (Join-Path $scriptDir ".." -Resolve) | Join-Path -ChildPath "packages/$packageName/resources/i18n/resjson"
if ($packageName -eq "web" -or $packageName -eq "desktop") {
$sourcePath = Join-Path $languageDirFullName "$packageName/i18n/resources.resjson"
if ($packageName -eq "web") {
$targetDir = Join-Path $scriptDir "../$packageName/dev-server/resources/i18n"
} else {
$targetDir = Join-Path $scriptDir "../$packageName/resources/i18n"
}
$targetPath = Join-Path $targetDir "resources.$languageId.json"
} else {
$sourcePath = Join-Path $languageDirFullName "packages/$packageName/i18n/resources.resjson"
$targetDir = Join-Path $scriptDir ".." -Resolve | Join-Path -ChildPath "packages/$packageName/resources/i18n/resjson"
$targetPath = Join-Path $targetDir "resources.$languageId.resjson"
}

Write-Verbose "Checking source path: $sourcePath"
if (Test-Path $sourcePath) {
Write-Verbose "Source path exists, preparing target directory"
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Force -Path $targetDir > $null
}
Write-Verbose "Checking $packageName source path: $sourcePath"
if (Test-Path $sourcePath) {
Write-Verbose "$packageName source path exists, preparing target directory"
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Force -Path $targetDir > $null
}

Write-Verbose "Copying file from $sourcePath to $targetPath"
Copy-Item -Path $sourcePath -Destination $targetPath
Write-Verbose "Copying file from $sourcePath to $targetPath"
Copy-Item -Path $sourcePath -Destination $targetPath

if ($packageName -ne "web" -and $packageName -ne "desktop") {
$jsonTargetDir = $targetDir.Replace("resjson", "json")
$jsonTargetPath = $targetPath.Replace("resjson", "json")

$jsonTargetPath = Join-Path $jsonTargetDir "resources.$languageId.json"
if (-not (Test-Path $jsonTargetDir)) {
New-Item -ItemType Directory -Force -Path $jsonTargetDir > $null
}

Write-Verbose "Converting resjson to json: $jsonTargetPath"
Convert-ResjsonToJson -sourcePath $sourcePath -targetPath $jsonTargetPath
} else {
$jsonTargetPath = $targetPath
}
}

# Handle the desktop directory exception
$desktopSource = Join-Path $($languageDir.FullName) "desktop/i18n/resources.resjson"
$desktopTargetDir = (Join-Path $scriptDir "../desktop/resources/i18n/resjson")
$desktopTarget = Join-Path $desktopTargetDir "resources.$languageId.resjson"

Write-Verbose "Checking desktop source path: $desktopSource"
if (Test-Path $desktopSource) {
Write-Verbose "Desktop source path exists, preparing target directory"
if (-not (Test-Path $desktopTargetDir)) {
New-Item -ItemType Directory -Force -Path $desktopTargetDir > $null
}

Write-Verbose "Copying file from $desktopSource to $desktopTarget"
Copy-Item -Path $desktopSource -Destination $desktopTarget
Write-Verbose "Converting $packageName resjson to json: $jsonTargetPath"
Convert-ResjsonToJson -sourcePath $sourcePath -targetPath $jsonTargetPath
}
}

$jsonDesktopTargetDir = $desktopTargetDir.Replace("resjson", "json")
$jsonDesktopTarget = $desktopTarget.Replace("resjson", "json")
Write-Host "Copying translation files"

if (-not (Test-Path $jsonDesktopTargetDir)) {
New-Item -ItemType Directory -Force -Path $jsonDesktopTargetDir > $null
}
foreach ($languageDir in $languageDirs) {
$languageId = $languageDir.Name
Write-Verbose "Processing language: $languageId"

Write-Verbose "Converting desktop resjson to json: $jsonDesktopTarget"
Convert-ResjsonToJson -sourcePath $desktopSource -targetPath $jsonDesktopTarget
# Copy files to each of the package directories
foreach ($packageName in $packageNames) {
Copy-Resources -packageName $packageName -languageDirFullName $($languageDir.FullName) -languageId $languageId
}
}
3 changes: 2 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"build:package": "npm run build:prod && npm run build-python && npm run package",
"build:prod": "cross-env NODE_ENV=production npm run build",
"build:test": "npm run build && npm run test",
"build-translations": "bux build-translations --src src --dest i18n",
"build-translations": "bux build-translations --src src --dest i18n --outputPath resources/i18n",
"watch": "npm run webpack -- --watch --progress --profile --colors --display-error-details --display-cached",
"electron": "electron build/client/main.js",
"electron:prod": "cross-env NODE_ENV=production electron build/client/main.js",
Expand All @@ -76,6 +76,7 @@
"workspace:build:package": "npm run build:package",
"workspace:build:prod": "npm run build:prod",
"workspace:build:test": "npm run build:test",
"workspace:build-translations": "npm run build-translations",
"workspace:clean": "npm run clean",
"workspace:launch:desktop": "npm run dev",
"workspace:lint": "npm run lint",
Expand Down
12 changes: 7 additions & 5 deletions desktop/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BrowserDependencyName } from "@batch/ui-react";
import { StorageAccountServiceImpl, SubscriptionServiceImpl } from "@batch/ui-service";
import { registerIcons } from "app/config";
import {
AppTranslationsLoaderService,
AuthorizationHttpService,
AuthService,
BatchAccountService,
Expand All @@ -31,9 +32,9 @@ import { Environment } from "common/constants";
import { Subject, combineLatest } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { DefaultBrowserEnvironment } from "@batch/ui-react/lib/environment";
import {StandardLocalizer} from "@batch/ui-common/lib/localization/standard-localizer";
import { LiveLocationService } from "@batch/ui-service/lib/location";
import { LiveResourceGroupService } from "@batch/ui-service/lib/resource-group";
import { DesktopLocalizer } from "./localizer/desktop-localizer";

@Component({
selector: "bl-app",
Expand Down Expand Up @@ -68,17 +69,18 @@ export class AppComponent implements OnInit, OnDestroy {
private ncjTemplateService: NcjTemplateService,
private predefinedFormulaService: PredefinedFormulaService,
private workspaceService: WorkspaceService,
private translationsLoaderService: AppTranslationsLoaderService
) {
// Initialize shared component lib environment
initEnvironment(new DefaultBrowserEnvironment(
// Initialize shared component lib environment
initEnvironment(new DefaultBrowserEnvironment(
{
mode: ENV === Environment.prod ? EnvironmentMode.Production : EnvironmentMode.Development
},
{
[DependencyName.Clock]: () => new StandardClock(),
// TODO: Create an adapter which hooks up to the desktop logger
[DependencyName.LoggerFactory]: () => createConsoleLogger,
[DependencyName.Localizer]: () => new StandardLocalizer(),
[DependencyName.Localizer]: () => new DesktopLocalizer(this.translationsLoaderService),
[DependencyName.HttpClient]:
() => new BatchExplorerHttpClient(authService),
[BrowserDependencyName.LocationService]: () =>
Expand All @@ -94,7 +96,7 @@ export class AppComponent implements OnInit, OnDestroy {
[BrowserDependencyName.FormLayoutProvider]:
() => new DefaultFormLayoutProvider(),
}
));
));

this.telemetryService.init(remote.getCurrentWindow().TELEMETRY_ENABLED);
this._initWorkspaces();
Expand Down
32 changes: 32 additions & 0 deletions desktop/src/app/localizer/desktop-localizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { app } from 'electron';
import { Localizer } from "@batch/ui-common/lib/localization";
import { AppTranslationsLoaderService } from "app/services";

export class DesktopLocalizer implements Localizer {
constructor(
private translationsLoaderService: AppTranslationsLoaderService
) { }

translate(message: string): string {
const translations = this.translationsLoaderService.translations;
if (!translations) {
throw new Error("Translation strings are not loaded: " + message);
}

const translation = translations.get(message);

if (translation != null) {
return translation;
} else {
return message;
}
}

getLocale(): string {
if (process.type === 'renderer') {
return navigator.language;
} else {
return app.getLocale();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ describe("ClientTranslationsLoaderService", () => {
it("it only loads the english translations file when locale is english", async () => {
localService.locale = "en";
await loader.load();
expect(fsSpy.readFile).toHaveBeenCalledTimes(1);
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "i18n-deprecated/resources.en.json"));
expect(fsSpy.readFile).toHaveBeenCalledTimes(2);
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "./resources/i18n/resources.en.json"));

expect(loader.translations.get("foo.banana")).toEqual("Banana");
expect(loader.translations.get("foo.potato")).toEqual("Potato");
Expand All @@ -69,8 +69,8 @@ describe("ClientTranslationsLoaderService", () => {
localService.locale = "fr";
await loader.load();
expect(fsSpy.readFile).toHaveBeenCalledTimes(2);
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "i18n-deprecated/resources.en.json"));
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "i18n-deprecated/resources.fr.json"));
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "./resources/i18n/resources.en.json"));
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "./resources/i18n/resources.fr.json"));

expect(loader.translations.get("foo.potato")).toEqual("Pomme de terre");
expect(loader.translations.get("foo.banana")).toEqual("Banana", "Use english translation when not present");
Expand All @@ -90,7 +90,7 @@ describe("ClientTranslationsLoaderService", () => {
localService.locale = "en";
await loader.load();
expect(devTranslationLoaderSpy.load).toHaveBeenCalledTimes(1);
expect(fsSpy.readFile).not.toHaveBeenCalled();
expect(fsSpy.readFile).toHaveBeenCalledTimes(1);

expect(loader.translations.get("foo.banana")).toEqual("Banana");
expect(loader.translations.get("foo.potato")).toEqual("Potato");
Expand All @@ -102,7 +102,7 @@ describe("ClientTranslationsLoaderService", () => {
await loader.load();
expect(devTranslationLoaderSpy.load).toHaveBeenCalledTimes(1);
expect(fsSpy.readFile).toHaveBeenCalledTimes(1);
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "i18n-deprecated/resources.fr.json"));
expect(fsSpy.readFile).toHaveBeenCalledWith(path.join(Constants.resourcesFolder, "./resources/i18n/resources.fr.json"));

expect(loader.translations.get("foo.potato")).toEqual("Pomme de terre");
expect(loader.translations.get("foo.banana")).toEqual("Banana", "Use english translation when not present");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class ClientTranslationsLoaderService extends TranslationsLoaderService {
}

private async _loadProductionTranslations() {
const englishTranslationFile = path.join(ClientConstants.resourcesFolder, "./i18n-deprecated/resources.en.json");
const englishTranslationFile = path.join(ClientConstants.resourcesFolder, "./resources/i18n/resources.en.json");
await this._loadProductionTranslationFile(englishTranslationFile);
await this._loadLocaleTranslations();
}
Expand All @@ -51,8 +51,7 @@ export class ClientTranslationsLoaderService extends TranslationsLoaderService {
*/
private async _loadLocaleTranslations() {
const locale = this.localeService.locale;
if (locale === Locale.English) { return; }
const localeTranslationFile = path.join(ClientConstants.resourcesFolder, `./i18n-deprecated/resources.${locale}.json`);
const localeTranslationFile = path.join(ClientConstants.resourcesFolder, `./resources/i18n/resources.${locale}.json`);
if (await this.fs.exists(localeTranslationFile)) {
await this._loadProductionTranslationFile(localeTranslationFile);
} else {
Expand Down
22 changes: 10 additions & 12 deletions docs/build-localization-locally.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
# Building Localization on Local Machine

## Building the English File
## Building the English File (Any Machine)

To build the latest English translation resjson file from the YAML files:
To build the latest English translation file from the YAML files:

* Run `npm run build` to build translations for the entire repository
* Run `npm run build-translations` in any package directory (desktop, packages/*) to build translations for a specific package
* Run `npm run build-translations`

The output will be in `{packageName}/i18n/resources.resjson`
* `web/dev-server/resources/i18n/resources.en.json` contains web English strings (web + all packages)
* `desktop/resources/i18n/resources.en.json` contains desktop English strings (desktop + all packages)

## Building Translations Files for Other Languages (Windows-Only)

To build the localization translations for all languages besides English:

* Follow the steps above first
* Follow the step above first (build the English file)
* Install the latest, recommended version of nuget.exe from <https://www.nuget.org/downloads> at C:\Users\{userName}, for instance.
* Navigate to the root of the repository
* Run `npm run loc:restore` to install all dependencies
* Run `npm run loc:build` to build the translations and move them to their correct directories
* If needed, run `npm run clean` to clear out all previously built translation files
* Run `npm run loc:build` to build the translations, move them to the package directories, and combine them altogether in one directory
* Run `npm run build-translations` to build the full, compiled translations for the web and desktop packages

The output will be in `{packageName}/resources/i18n`

* `{packageName}/resources/i18n/resjson` contains RESJSON translations
* `{packageName}/resources/i18n/json` contains JSON translations (RESJSON syntax and comments have been stripped out)
* `web/dev-server/resources/i18n` contains web translations (web + all packages)
* `desktop/resources/i18n` contains desktop translations (desktop + all packages)
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"build:package": "lerna run workspace:build:package --stream",
"build:prod": "lerna run workspace:build:prod --stream",
"build:test": "lerna run workspace:build:test --stream",
"clean": "lerna run --parallel workspace:clean --stream && npm run loc:clean",
"build-translations": "lerna run workspace:build-translations --stream",
"clean": "lerna run --parallel workspace:clean --stream && bux rmrf ./Localize/out",
"gather-build-results": "bux gather-build-results",
"launch": "npm run -s launch:web",
"launch:desktop": "lerna run --parallel workspace:launch:desktop --stream",
Expand All @@ -64,7 +65,6 @@
"lint:fix": "prettier -w . && lerna run --parallel workspace:lint:fix --stream && npm run -s lint:markdown",
"lint:markdown": "markdownlint-cli2 \"**/*.md\" \"#**/node_modules/**/*\" \"#SECURITY.md\"",
"loc:build": "powershell -ExecutionPolicy Bypass -File ./Localize/build.ps1 && powershell -ExecutionPolicy Bypass -File ./Localize/copy-translations.ps1",
"loc:clean": "bux rmrf ./Localize/out",
"loc:restore": "cd Localize && powershell -ExecutionPolicy Bypass -File restore.ps1",
"start": "npm run -s start:web",
"start:desktop": "lerna run --parallel workspace:start:desktop --stream",
Expand Down
4 changes: 1 addition & 3 deletions packages/common/i18n/resources.resjson
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"lib.common.localizer.account": "Account",
"lib.common.localizer.resourceGroup": "Resource Group",
"lib.common.localizer.subscription": "Subscription"
"lib.common.form.validationError": "Value must be a boolean"
}
3 changes: 2 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build": "npm run compile && npm run build-translations",
"build:clean": "run-s clean compile",
"build:test": "run-s build test",
"build-translations": "bux build-translations --src src/ui-common --dest i18n --packageName lib.common",
"build-translations": "bux build-translations --src src/ui-common --dest i18n --outputPath resources/i18n/json --packageName lib.common",
"compile": "run-p compile:*",
"compile:esm": "tsc -b ./config/tsconfig.build.json",
"compile:cjs": "tsc -b ./config/tsconfig.cjs.json",
Expand All @@ -41,6 +41,7 @@
"workspace:build:package": "npm run build:clean",
"workspace:build:prod": "npm run build:clean",
"workspace:build:test": "npm run build:test",
"workspace:build-translations": "npm run build-translations",
"workspace:clean": "npm run clean",
"workspace:launch:desktop": "npm run watch",
"workspace:launch:web": "npm run watch",
Expand Down
Loading