Repository navigation
Expand file tree
/
Copy pathLocalizationController.ts
More file actions
284 lines (242 loc) · 9.3 KB
/
Copy pathLocalizationController.ts
File metadata and controls
284 lines (242 loc) · 9.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
import { localizationStore } from '@/Controllers/Localization/LocalizationStore'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { PreferencesController } from '@/Controllers/PreferencesController'
import { AbstractViewController } from '@/Controllers/Abstract/AbstractViewController'
import { DEFAULT_LOCALE, resolveLocale } from '@standardnotes/i18n'
import { getWebLocaleService } from '@/Controllers/Localization/WebLocaleService'
import { configureDateLocale } from '@/Utils/DateLocale'
import { syncDesktopMainProcessLocalization } from '@/Application/Device/SyncDesktopMainProcessLocalization'
import { syncMobileNativeLocalization } from '@/Application/Device/SyncMobileNativeLocalization'
import {
clearPersistedLocale,
getPersistedLocale,
hasSeenLabsSpotlight,
markLabsSpotlightSeen,
persistLocale,
setPendingLocaleSwitch,
takePendingLocaleSwitch,
} from '@/Utils/LocalePersistence'
import { addToast, ToastType } from '@standardnotes/toast'
import { ElementIds } from '@/Constants/ElementIDs'
import { RouteServiceInterface, RouteType } from '@standardnotes/ui-services'
import { c } from 'ttag'
import { action, makeObservable, observable, runInAction } from 'mobx'
import {
ApplicationEvent,
InternalEventBusInterface,
InternalEventHandlerInterface,
InternalEventInterface,
} from '@standardnotes/snjs'
export class LocalizationController extends AbstractViewController implements InternalEventHandlerInterface {
labsSpotlightOpen = false
private initializedWithLocalizationDisabled = false
private isTogglingLocalization = false
private availableLocalesRequested = false
constructor(
private readonly featuresController: FeaturesController,
private readonly workspaceIdentifier: string,
private readonly preferencesController: PreferencesController,
private readonly routeService: RouteServiceInterface,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
makeObservable(this, {
labsSpotlightOpen: observable,
dismissLabsSpotlight: action,
openLanguageSettingsFromLabsSpotlight: action,
})
eventBus.addEventHandler(this, ApplicationEvent.FeaturesAvailabilityChanged)
eventBus.addEventHandler(this, ApplicationEvent.LocalDataLoaded)
eventBus.addEventHandler(this, ApplicationEvent.SignedOut)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
switch (event.type) {
case ApplicationEvent.LocalDataLoaded:
case ApplicationEvent.FeaturesAvailabilityChanged:
await this.initializeLocalization()
this.setLabsSpotlightOpen(this.shouldShowLabsSpotlight())
break
case ApplicationEvent.SignedOut:
clearPersistedLocale(this.workspaceIdentifier)
break
}
}
dismissLabsSpotlight = (): void => {
this.setLabsSpotlightOpen(false)
markLabsSpotlightSeen()
}
openLanguageSettingsFromLabsSpotlight = (): void => {
this.dismissLabsSpotlight()
this.preferencesController.openPreferencesAndScrollToSection(ElementIds.LanguagePreferencesSection, 'general')
}
private shouldShowLabsSpotlight(): boolean {
const routeType = this.routeService.getRoute().type
return (
!this.featuresController.isLocalizationEnabled() &&
!hasSeenLabsSpotlight() &&
routeType !== RouteType.Purchase &&
routeType !== RouteType.Settings
)
}
private setLabsSpotlightOpen(open: boolean): void {
runInAction(() => {
this.labsSpotlightOpen = open
})
}
async changeLocaleAndReload(locale: string): Promise<boolean> {
if (locale === localizationStore.currentLocale && locale === getPersistedLocale(this.workspaceIdentifier)) {
return false
}
if (!this.featuresController.isLocalizationEnabled()) {
return false
}
let resolvedLocale: string
try {
resolvedLocale = await this.loadLocale(locale)
} catch (error) {
console.error('Failed to load locale before reload', error)
addToast({
type: ToastType.Error,
message: c('B6.Preferences.General.Language.Error').t`Could not load the selected language.`,
})
return false
}
persistLocale(this.workspaceIdentifier, resolvedLocale)
this.reloadAndShowToast(resolvedLocale)
return true
}
/**
* Resolves to `true` if the page is reloading, or `false` if the toggle was applied in place because the app is
* already displayed in the target language.
*/
async toggleLocalization(waitBeforeReload: Promise<void>): Promise<boolean> {
this.isTogglingLocalization = true
const activeLocale = localizationStore.currentLocale
this.featuresController.toggleLocalization()
if (this.featuresController.isLocalizationEnabled()) {
try {
await this.loadLocale(getPersistedLocale(this.workspaceIdentifier))
} catch (error) {
console.error('Failed to load locale before reload', error)
}
}
await waitBeforeReload
this.isTogglingLocalization = false
return this.initializeLocalization(activeLocale)
}
private reloadAndShowToast(locale: string): void {
if (locale !== localizationStore.currentLocale) {
setPendingLocaleSwitch(locale)
}
window.location.reload()
}
private async showLocaleSwitchedToast(locale: string): Promise<void> {
let languageName = locale
try {
languageName = (await getWebLocaleService().getAvailableLocales())[locale] ?? locale
} catch (error) {
console.error('Failed to load locale catalog', error)
}
addToast({
type: ToastType.Success,
message: c('B6.Preferences.General.Language.Info').t`Language switched to ${languageName}`,
})
}
private async loadLocale(locale: string | undefined): Promise<string> {
const localeService = getWebLocaleService()
const catalog = await localeService.getAvailableLocales()
const resolvedLocale = resolveLocale({
savedLocale: locale,
availableLocales: Object.keys(catalog),
})
await localeService.loadAndActivateLocale(resolvedLocale)
return resolvedLocale
}
private updatePersistedLocale(localizationEnabled: boolean, locale: string): boolean {
const persistedLocale = getPersistedLocale(this.workspaceIdentifier)
if (!localizationEnabled) {
if (!persistedLocale) {
return false
}
clearPersistedLocale(this.workspaceIdentifier)
return true
}
if (persistedLocale === locale) {
return false
}
persistLocale(this.workspaceIdentifier, locale)
return true
}
/** Loaded while localization is still off so the language dropdown can show as soon as it's turned on. */
private async preloadAvailableLocales(): Promise<void> {
if (this.availableLocalesRequested) {
return
}
this.availableLocalesRequested = true
try {
const catalog = await getWebLocaleService().getAvailableLocales()
localizationStore.setAvailableLocales(catalog)
} catch (error) {
this.availableLocalesRequested = false
console.error('Failed to load locale catalog', error)
localizationStore.setAvailableLocalesFailedToLoad()
}
}
/**
* Resolves to `true` if the page is reloading. Pass `activeLocale` when a locale was activated in memory ahead of a
* reload, so it isn't mistaken for the one displayed.
*/
private async initializeLocalization(activeLocale = getWebLocaleService().getCurrentLocale()): Promise<boolean> {
if (this.isTogglingLocalization) {
return false
}
void this.preloadAvailableLocales()
const localizationEnabled = this.featuresController.isLocalizationEnabled()
if (!localizationEnabled && this.initializedWithLocalizationDisabled) {
return false
}
this.initializedWithLocalizationDisabled = !localizationEnabled
const localeService = getWebLocaleService()
try {
const catalog = localizationEnabled ? await localeService.getAvailableLocales() : undefined
const locale = catalog
? resolveLocale({
savedLocale: getPersistedLocale(this.workspaceIdentifier),
availableLocales: Object.keys(catalog),
})
: DEFAULT_LOCALE
const persistedLocaleChanged = this.updatePersistedLocale(localizationEnabled, locale)
if (locale !== activeLocale && (persistedLocaleChanged || !localizationEnabled)) {
this.reloadAndShowToast(locale)
return true
}
if (catalog) {
localizationStore.setAvailableLocales(catalog)
}
} catch (error) {
console.error('Failed to load locale catalog', error)
localizationStore.setAvailableLocalesFailedToLoad()
}
if (takePendingLocaleSwitch() === activeLocale) {
void this.showLocaleSwitchedToast(activeLocale)
}
await this.syncDateFormatting(activeLocale)
localizationStore.setCurrentLocale(activeLocale)
this.syncNativeShellLocalization(localizationEnabled, activeLocale)
return false
}
private syncNativeShellLocalization(localizationEnabled: boolean, locale: string): void {
const state = {
localizationEnabled,
locale: localizationEnabled ? locale : undefined,
}
syncDesktopMainProcessLocalization(state)
syncMobileNativeLocalization(state)
}
private async syncDateFormatting(appLocale: string): Promise<void> {
await configureDateLocale({
appLocale,
localizationEnabled: this.featuresController.isLocalizationEnabled(),
})
}
}