From 3d6efe80f7202f35e05817824e9de7af2f187279 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Tue, 27 Oct 2026 11:49:46 -0300 Subject: [PATCH 1/2] feat: Introduces localization with support for multiple languages --- .../InternalFeatures/InternalFeature.ts | 1 - .../src/javascripts/Application/DevMode.ts | 4 +-- .../Preferences/Panes/General/General.tsx | 2 +- .../Controllers/FeaturesController.ts | 13 +-------- .../LocalizationController.spec.ts | 27 +++---------------- .../Localization/LocalizationController.ts | 3 +-- packages/web/src/javascripts/FeatureTrunk.ts | 6 +---- 7 files changed, 8 insertions(+), 48 deletions(-) diff --git a/packages/services/src/Domain/InternalFeatures/InternalFeature.ts b/packages/services/src/Domain/InternalFeatures/InternalFeature.ts index 323abd38618..1dfa14b229d 100644 --- a/packages/services/src/Domain/InternalFeatures/InternalFeature.ts +++ b/packages/services/src/Domain/InternalFeatures/InternalFeature.ts @@ -1,4 +1,3 @@ export enum InternalFeature { Vaults = 'vaults', - Localization = 'localization', } diff --git a/packages/web/src/javascripts/Application/DevMode.ts b/packages/web/src/javascripts/Application/DevMode.ts index 97c7e084ed2..a7c5605447f 100644 --- a/packages/web/src/javascripts/Application/DevMode.ts +++ b/packages/web/src/javascripts/Application/DevMode.ts @@ -3,9 +3,7 @@ import { WebApplicationInterface } from '@standardnotes/ui-services' export class DevMode { constructor(private application: WebApplicationInterface) { - InternalFeatureService.get().enableFeature(InternalFeature.Vaults) - InternalFeatureService.get().enableFeature(InternalFeature.Localization) - } + InternalFeatureService.get().enableFeature(InternalFeature.Vaults) } /** Valid only when running a mock event publisher on port 3124 */ async purchaseMockSubscription() { diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/General/General.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/General/General.tsx index 57edda7a93c..0ef6889b61b 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/General/General.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/General/General.tsx @@ -22,7 +22,7 @@ const General: FunctionComponent = () => { - {application.featuresController.isLocalizationFeatureAvailable() && } + diff --git a/packages/web/src/javascripts/Controllers/FeaturesController.ts b/packages/web/src/javascripts/Controllers/FeaturesController.ts index 87ea26aae2b..d2f53aeca72 100644 --- a/packages/web/src/javascripts/Controllers/FeaturesController.ts +++ b/packages/web/src/javascripts/Controllers/FeaturesController.ts @@ -13,7 +13,7 @@ import { import { action, makeObservable, observable, runInAction, when } from 'mobx' import { AbstractViewController } from './Abstract/AbstractViewController' import { CrossControllerEvent } from './CrossControllerEvent' -import { featureTrunkLocalizationEnabled, featureTrunkVaultsEnabled } from '@/FeatureTrunk' +import { featureTrunkVaultsEnabled } from '@/FeatureTrunk' export class FeaturesController extends AbstractViewController implements InternalEventHandlerInterface { hasFolders: boolean @@ -148,22 +148,11 @@ export class FeaturesController extends AbstractViewController implements Intern ) } - isLocalizationFeatureAvailable(): boolean { - return ( - featureTrunkLocalizationEnabled() || - this.features.hasRole(RoleName.create(RoleName.NAMES.InternalTeamUser).getValue()) - ) - } - toggleLocalization(): void { this.features.toggleExperimentalFeature(NativeFeatureIdentifier.TYPES.Localization) } isLocalizationEnabled(): boolean { - if (!this.isLocalizationFeatureAvailable()) { - return false - } - return this.features.isExperimentalFeatureEnabled(NativeFeatureIdentifier.TYPES.Localization) } diff --git a/packages/web/src/javascripts/Controllers/Localization/LocalizationController.spec.ts b/packages/web/src/javascripts/Controllers/Localization/LocalizationController.spec.ts index 8b0687eacdc..4dfdcb51994 100644 --- a/packages/web/src/javascripts/Controllers/Localization/LocalizationController.spec.ts +++ b/packages/web/src/javascripts/Controllers/Localization/LocalizationController.spec.ts @@ -29,7 +29,6 @@ describe('LocalizationController', () => { let reload: jest.Mock let localizationEnabled: boolean let syncedLocale: string | undefined - let localizationAvailable: boolean let seenOnAccount: boolean let seenOnDevice: boolean @@ -46,7 +45,6 @@ describe('LocalizationController', () => { const featuresController = { isLocalizationEnabled: () => localizationEnabled, - isLocalizationFeatureAvailable: () => localizationAvailable, toggleLocalization: () => { localizationEnabled = !localizationEnabled }, @@ -86,7 +84,6 @@ describe('LocalizationController', () => { localizationEnabled = true syncedLocale = undefined - localizationAvailable = true seenOnAccount = true seenOnDevice = false preferences.setValue.mockClear() @@ -171,21 +168,6 @@ describe('LocalizationController', () => { expect(localizationStore.availableLocales).toEqual(catalog) }) - it('loads the language list once the feature becomes available', async () => { - localizationEnabled = false - localizationAvailable = false - mockLocaleService.getAvailableLocales.mockClear() - - const controller = await bootWithLocale('en_US') - expect(mockLocaleService.getAvailableLocales).not.toHaveBeenCalled() - - localizationAvailable = true - await sendEvent(controller, ApplicationEvent.FeaturesAvailabilityChanged) - await flushPromises() - - expect(localizationStore.availableLocales).toEqual(catalog) - }) - it('keeps the saved language and reports an error when the catalog fails to load', async () => { persistLocale('fr_FR') mockLocaleService.getAvailableLocales.mockRejectedValue(new Error('offline')) @@ -234,9 +216,8 @@ describe('LocalizationController', () => { return controller } - const signInAndGainAccess = async (controller: LocalizationController) => { + const signIn = async (controller: LocalizationController) => { await sendEvent(controller, ApplicationEvent.SignedIn) - localizationAvailable = true await sendEvent(controller, ApplicationEvent.FeaturesAvailabilityChanged) } @@ -262,10 +243,9 @@ describe('LocalizationController', () => { }) it('does not open on sign in when the synced pref says it was already seen', async () => { - localizationAvailable = false const controller = await launch() - await signInAndGainAccess(controller) + await signIn(controller) expect(controller.labsSpotlightOpen).toBe(false) seenOnAccount = true @@ -276,10 +256,9 @@ describe('LocalizationController', () => { }) it('opens after the first sync following sign in when it has not been seen', async () => { - localizationAvailable = false const controller = await launch() - await signInAndGainAccess(controller) + await signIn(controller) expect(controller.labsSpotlightOpen).toBe(false) await sendEvent(controller, ApplicationEvent.CompletedFullSync) diff --git a/packages/web/src/javascripts/Controllers/Localization/LocalizationController.ts b/packages/web/src/javascripts/Controllers/Localization/LocalizationController.ts index ebe86f799ad..d2c525064e1 100644 --- a/packages/web/src/javascripts/Controllers/Localization/LocalizationController.ts +++ b/packages/web/src/javascripts/Controllers/Localization/LocalizationController.ts @@ -130,7 +130,6 @@ export class LocalizationController extends AbstractViewController implements In const routeType = this.routeService.getRoute().type return ( - this.featuresController.isLocalizationFeatureAvailable() && !this.featuresController.isLocalizationEnabled() && !this.hasSeenLabsSpotlightOnAccount() && !this.hasSeenLabsSpotlightOnDevice() && @@ -272,7 +271,7 @@ export class LocalizationController extends AbstractViewController implements In /** Loaded while localization is still off so the language dropdown can show as soon as it's turned on. */ private async preloadAvailableLocales(): Promise { - if (this.availableLocalesRequested || !this.featuresController.isLocalizationFeatureAvailable()) { + if (this.availableLocalesRequested) { return } this.availableLocalesRequested = true diff --git a/packages/web/src/javascripts/FeatureTrunk.ts b/packages/web/src/javascripts/FeatureTrunk.ts index a1d56361cab..5969135daf3 100644 --- a/packages/web/src/javascripts/FeatureTrunk.ts +++ b/packages/web/src/javascripts/FeatureTrunk.ts @@ -10,8 +10,4 @@ export function featureTrunkEnabled(trunk: FeatureTrunkName): boolean { export function featureTrunkVaultsEnabled(): boolean { return InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults) -} - -export function featureTrunkLocalizationEnabled(): boolean { - return InternalFeatureService.get().isFeatureEnabled(InternalFeature.Localization) -} +} \ No newline at end of file From c5f6c44e0ebadc6671965d88836b4076a1bd2701 Mon Sep 17 00:00:00 2001 From: Antonella Sgarlatta Date: Thu, 29 Oct 2026 23:50:11 -0300 Subject: [PATCH 2/2] chore: fix eslint error --- packages/web/src/javascripts/Application/DevMode.ts | 3 ++- packages/web/src/javascripts/FeatureTrunk.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/web/src/javascripts/Application/DevMode.ts b/packages/web/src/javascripts/Application/DevMode.ts index a7c5605447f..ead3f49c5f6 100644 --- a/packages/web/src/javascripts/Application/DevMode.ts +++ b/packages/web/src/javascripts/Application/DevMode.ts @@ -3,7 +3,8 @@ import { WebApplicationInterface } from '@standardnotes/ui-services' export class DevMode { constructor(private application: WebApplicationInterface) { - InternalFeatureService.get().enableFeature(InternalFeature.Vaults) } + InternalFeatureService.get().enableFeature(InternalFeature.Vaults) + } /** Valid only when running a mock event publisher on port 3124 */ async purchaseMockSubscription() { diff --git a/packages/web/src/javascripts/FeatureTrunk.ts b/packages/web/src/javascripts/FeatureTrunk.ts index 5969135daf3..b8e6128c128 100644 --- a/packages/web/src/javascripts/FeatureTrunk.ts +++ b/packages/web/src/javascripts/FeatureTrunk.ts @@ -10,4 +10,4 @@ export function featureTrunkEnabled(trunk: FeatureTrunkName): boolean { export function featureTrunkVaultsEnabled(): boolean { return InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults) -} \ No newline at end of file +}