Skip to content

Commit 3705e56

Browse files
antsgarL10n bot
andauthored
chore: Adds localization behind internal feature flag (#3059)
* feat: Adds new i18n package and locale resolver * feat: Adds locale service * i18n(weekly-mr): Upgrade translations from crowdin (e1e05408). * feat: Adds copy plugin pattern * feat: Adds experimental feature flag and preferences section * feat: Adds injector and mobx store * feat: Adds boot flow * feat: Adds locale preference * Revert "i18n(weekly-mr): Upgrade translations from crowdin (e1e05408)." This reverts commit 0f30f58. * i18n(weekly-mr): Upgrade translations from crowdin (fa494166). * Revert "i18n(weekly-mr): Upgrade translations from crowdin (fa494166)." This reverts commit e1bd950. * i18n(weekly-mr): Upgrade translations from crowdin (fa494166). * feat: Adds language selector and re-renders strings on locale update * feat: Adds error toast when loading language fails * feat: Localize date time strings * feat: Add fixes for specific string re-renders * feat: Localize dates that are pulled from models * feat: Fixes localization for snjs strings * feat: Add desktop localization * feat: Add mobile localization * fix: Fixes strings not re-rendering on locale change * feat: Fixes toggle and reloading behavior * feat: Update setting copy * feat: Adds spotlight modal * feat: Adds internal feature flag * feat: Fix modal styling * feat: Updates copy * feat: Loads persisted locale before app boot * fix: Avoids redundant desktop locale syncs and restores date-at-time format * feat: Improves service code * chore: Adds shared type * chore: Improves spotlight code * chore: Improves date handling code * chore: Small code tweaks * chore: Revert unrelated changes * fix: Fixes regressions * fix: Fixes regressions * fix: Fixes regressions * Revert "i18n(weekly-mr): Upgrade translations from crowdin (fa494166)." This reverts commit bbcc72c. * i18n(weekly-mr): Upgrade translations from crowdin (0927732f). * chore: Code and toggle animation improvements * chore: Add controller tests * chore: fixes eslint errors --------- Co-authored-by: L10n bot <service_account_73c4205ebb9a547d11aa5c7b26226660@noreply.gitlab.protontech.ch>
1 parent c680f4f commit 3705e56

136 files changed

Lines changed: 50498 additions & 272 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ packages/responses/dist
2727
packages/sncrypto-common/dist
2828
packages/sncrypto-web/dist
2929
packages/snjs/dist
30+
packages/i18n/dist
3031

3132
**/.pnp.*
3233
**/.yarn/*

‎packages/.locale-state.metadata‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"project": "fe-standardnotes",
3+
"locale": "ac161306578fcd17ec1c93f67b26c6d4516d4232"
4+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { addLocale, LocaleData, setDefaultLang, useLocale } from 'ttag'
2+
import fs from 'fs/promises'
3+
import path from 'path'
4+
import { NativeLocalizationState } from '@standardnotes/snjs'
5+
import { reinitializeStrings } from '../Strings'
6+
7+
const DEFAULT_LOCALE = 'en_US'
8+
9+
const loadedLocales = new Set<string>()
10+
let appliedLocale = DEFAULT_LOCALE
11+
let onStringsReloaded: (() => void) | undefined
12+
13+
setDefaultLang(DEFAULT_LOCALE)
14+
15+
export function registerMainProcessLocalizationCallbacks(callbacks: { onStringsReloaded: () => void }): void {
16+
onStringsReloaded = callbacks.onStringsReloaded
17+
}
18+
19+
const WEB_LOCALE_FILE_PATTERN = /^(config\/locales|[a-z]{2,3}_[A-Za-z0-9]{2,4})\.json$/
20+
21+
function getLocalesDirectory(): string {
22+
return path.join(__dirname, 'locales')
23+
}
24+
25+
export async function readWebLocaleFile(relativePath: string): Promise<string> {
26+
if (!WEB_LOCALE_FILE_PATTERN.test(relativePath)) {
27+
throw new Error(`Invalid web locale file: ${relativePath}`)
28+
}
29+
30+
return fs.readFile(path.join(__dirname, 'web', 'locales', relativePath), 'utf8')
31+
}
32+
33+
async function activateLocale(locale: string): Promise<void> {
34+
if (locale !== DEFAULT_LOCALE && !loadedLocales.has(locale)) {
35+
const filePath = path.join(getLocalesDirectory(), `${locale}.json`)
36+
const contents = await fs.readFile(filePath, 'utf8')
37+
addLocale(locale, JSON.parse(contents) as LocaleData)
38+
loadedLocales.add(locale)
39+
}
40+
41+
useLocale(locale)
42+
}
43+
44+
export async function syncMainProcessLocalization(state: NativeLocalizationState): Promise<void> {
45+
const locale = state.localizationEnabled && state.locale ? state.locale : DEFAULT_LOCALE
46+
if (locale === appliedLocale) {
47+
return
48+
}
49+
50+
try {
51+
await activateLocale(locale)
52+
appliedLocale = locale
53+
} catch (error) {
54+
console.error(`Failed to load desktop locale pack for ${locale}`, error)
55+
useLocale(DEFAULT_LOCALE)
56+
appliedLocale = DEFAULT_LOCALE
57+
}
58+
59+
reinitializeStrings()
60+
onStringsReloaded?.()
61+
}

‎packages/desktop/app/javascripts/Main/Menus/Menus.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ function windowMenu(store: Store, trayManager: TrayManager, reload: () => void):
323323
role: Roles.Window,
324324
submenu: [
325325
menuRole(str().minimize, Roles.Minimize),
326-
menuRole(str().close, Roles.Close),
326+
menuRole(isMac() ? str().closeWindow : str().close, Roles.Close),
327327
Separator,
328328
...(isMac() ? macWindowItems() : [minimizeToTrayItem(store, trayManager, reload)]),
329329
],

‎packages/desktop/app/javascripts/Main/Remote/RemoteBridge.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { Component, PackageManagerInterface } from '../Packages/PackageManagerIn
2121
import { SearchManagerInterface } from '../Search/SearchManagerInterface'
2222
import { RemoteDataInterface } from './DataInterface'
2323
import { MediaManagerInterface } from '../Media/MediaManagerInterface'
24+
import { readWebLocaleFile, syncMainProcessLocalization } from '../Localization/MainProcessLocalization'
25+
import { NativeLocalizationState } from '@standardnotes/snjs'
2426

2527
/**
2628
* Read https://github.com/electron/remote to understand how electron/remote works.
@@ -92,6 +94,8 @@ export class RemoteBridge implements CrossProcessBridge {
9294
getHomeServerLogs: this.getHomeServerLogs.bind(this),
9395
getHomeServerUrl: this.getHomeServerUrl.bind(this),
9496
getHomeServerLastErrorMessage: this.getHomeServerLastErrorMessage.bind(this),
97+
syncMainProcessLocalization: this.syncMainProcessLocalization.bind(this),
98+
readWebLocaleFile: this.readWebLocaleFile.bind(this),
9599
}
96100
}
97101

@@ -302,4 +306,12 @@ export class RemoteBridge implements CrossProcessBridge {
302306
async getHomeServerLastErrorMessage(): Promise<string | undefined> {
303307
return this.homeServerManager.getHomeServerLastErrorMessage()
304308
}
309+
310+
async syncMainProcessLocalization(state: NativeLocalizationState): Promise<void> {
311+
await syncMainProcessLocalization(state)
312+
}
313+
314+
async readWebLocaleFile(relativePath: string): Promise<string> {
315+
return readWebLocaleFile(relativePath)
316+
}
305317
}

‎packages/desktop/app/javascripts/Main/Strings/index.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export function initializeStrings(_locale: string): void {
2323
strings = createStrings()
2424
}
2525

26+
/** Rebuilds menu/tray strings after the active ttag locale changes. */
27+
export function reinitializeStrings(): void {
28+
strings = createStrings()
29+
}
30+
2631
export function str(): Strings {
2732
if (isDev()) {
2833
if (!strings) {

‎packages/desktop/app/javascripts/Main/Strings/strings.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function createStrings(): Strings {
5454
clearCacheAndReload: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Clear Cache and Reload`,
5555
speech: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Speech`,
5656
close: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Close`,
57+
closeWindow: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Close Window`,
5758
minimize: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Minimize`,
5859
zoom: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Zoom`,
5960
bringAllToFront: c('B8.MobileDesktopShared.Desktop.Menu.Label').t`Bring All to Front`,

‎packages/desktop/app/javascripts/Main/Strings/types.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ interface AppMenuStrings {
5050
clearCacheAndReload: string
5151
speech: string
5252
close: string
53+
closeWindow: string
5354
minimize: string
5455
zoom: string
5556
bringAllToFront: string

‎packages/desktop/app/javascripts/Main/TrayManager.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ export interface TrayManager {
1212
shouldMinimizeToTray(): boolean
1313
createTrayIcon(): void
1414
destroyTrayIcon(): void
15+
reloadLabels(): void
1516
}
1617

1718
export function createTrayManager(window: Electron.BrowserWindow, store: Store): TrayManager {
1819
let tray: Tray | undefined
1920
let updateContextMenu: (() => void) | undefined
21+
let applyTrayLabels: (() => void) | undefined
2022

2123
function showWindow() {
2224
window.show()
@@ -70,6 +72,15 @@ export function createTrayManager(window: Electron.BrowserWindow, store: Store):
7072
},
7173
])
7274

75+
applyTrayLabels = function applyTrayLabels() {
76+
trayContextMenu.getMenuItemById(SHOW_WINDOW_ID)!.label = str().show
77+
trayContextMenu.getMenuItemById(HIDE_WINDOW_ID)!.label = str().hide
78+
const quitItem = trayContextMenu.items.find((item) => item.role === 'quit')
79+
if (quitItem) {
80+
quitItem.label = str().quit
81+
}
82+
}
83+
7384
updateContextMenu = function updateContextMenu() {
7485
if (window.isVisible()) {
7586
trayContextMenu.getMenuItemById(SHOW_WINDOW_ID)!.visible = false
@@ -105,6 +116,12 @@ export function createTrayManager(window: Electron.BrowserWindow, store: Store):
105116
tray!.destroy()
106117
tray = undefined
107118
updateContextMenu = undefined
119+
applyTrayLabels = undefined
120+
},
121+
122+
reloadLabels() {
123+
applyTrayLabels?.()
124+
updateContextMenu?.()
108125
},
109126
}
110127
}

‎packages/desktop/app/javascripts/Main/Window.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { initializeZoomManager } from './ZoomManager'
2727
import { HomeServerManager } from './HomeServer/HomeServerManager'
2828
import { FilesManager } from './File/FilesManager'
2929
import { DirectoryManager } from './Directory/DirectoryManager'
30+
import { registerMainProcessLocalizationCallbacks } from './Localization/MainProcessLocalization'
3031

3132
const WINDOW_DEFAULT_WIDTH = 1100
3233
const WINDOW_DEFAULT_HEIGHT = 800
@@ -224,6 +225,13 @@ async function createWindowServices(window: Electron.BrowserWindow, appState: Ap
224225
spellcheckerManager,
225226
})
226227

228+
registerMainProcessLocalizationCallbacks({
229+
onStringsReloaded: () => {
230+
menuManager.reload()
231+
trayManager.reloadLabels()
232+
},
233+
})
234+
227235
const fileBackupsManager = new FilesBackupManager(appState, window.webContents, filesManager)
228236

229237
return {

0 commit comments

Comments
 (0)