Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 55 additions & 1 deletion packages/i18n/src/DateLocaleResolver.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { resolveActiveDateLocales, toDayjsLocale, toIntlLocale } from './DateLocaleResolver'
import { resolveActiveDateLocales, resolveIntlLocale, toDayjsLocale, toIntlLocale } from './DateLocaleResolver'

describe('toIntlLocale', () => {
it('maps known app locales to BCP 47 tags', () => {
Expand Down Expand Up @@ -26,7 +26,61 @@ describe('toDayjsLocale', () => {
})
})

describe('resolveIntlLocale', () => {
it('uses a preferred language that matches the app language regardless of region', () => {
expect(resolveIntlLocale('en_US', ['en-GB'])).toBe('en-GB')
expect(resolveIntlLocale('es_LA', ['es-AR'])).toBe('es-AR')
expect(resolveIntlLocale('pt_BR', ['pt-PT'])).toBe('pt-PT')
expect(resolveIntlLocale('en_US', ['en'])).toBe('en')
})

it('uses the first matching preferred language', () => {
expect(resolveIntlLocale('en_US', ['fr-FR', 'en-AU', 'en-GB'])).toBe('en-AU')
})

it('falls back to the app locale when no preferred language matches', () => {
expect(resolveIntlLocale('en_US', ['fr-FR', 'de-DE'])).toBe('en-US')
expect(resolveIntlLocale('es_LA', [])).toBe('es-419')
expect(resolveIntlLocale('fr_FR')).toBe('fr-FR')
})

it('does not match a preferred language written in a different script', () => {
expect(resolveIntlLocale('zh_CN', ['zh-TW', 'zh-HK'])).toBe('zh-CN')
expect(resolveIntlLocale('zh_CN', ['zh-TW', 'zh-SG'])).toBe('zh-SG')
})

it('ignores invalid preferred language tags', () => {
expect(resolveIntlLocale('en_US', ['not a locale', 'en-GB'])).toBe('en-GB')
})
})

describe('resolveActiveDateLocales', () => {
it('uses the preferred regional variant for intl and the app locale for dayjs', () => {
expect(
resolveActiveDateLocales({
appLocale: 'en_US',
localizationEnabled: true,
preferredLocales: ['en-GB'],
}),
).toEqual({
intlLocale: 'en-GB',
dayjsLocale: 'en',
})
})

it('ignores preferred languages when localization is disabled', () => {
expect(
resolveActiveDateLocales({
appLocale: 'en_US',
localizationEnabled: false,
preferredLocales: ['en-GB'],
}),
).toEqual({
intlLocale: undefined,
dayjsLocale: 'en',
})
})

it('uses app locale mappings when localization is enabled', () => {
expect(
resolveActiveDateLocales({
Expand Down
28 changes: 27 additions & 1 deletion packages/i18n/src/DateLocaleResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,38 @@ export type ResolvedDateLocales = {
export type ResolveActiveDateLocalesOptions = {
appLocale: string
localizationEnabled: boolean
/** The user's preferred languages, in order, e.g. `navigator.languages`. */
preferredLocales?: readonly string[]
}

export function toIntlLocale(appLocale: string): string {
return INTL_LOCALE_OVERRIDES[appLocale] ?? appLocale.replace('_', '-')
}

/** Compares script too, so a Traditional Chinese preference doesn't format dates next to a Simplified Chinese UI. */
function toLanguageAndScript(tag: string): string | undefined {
try {
const { language, script } = new Intl.Locale(tag).maximize()
return `${language}-${script}`
} catch {
return undefined
}
}

/**
* Prefers the user's own regional variant of the app language (e.g. `en-GB` for an `en_US` UI) so dates follow their
* regional conventions, falling back to the app locale when none of their preferred languages match it.
*/
export function resolveIntlLocale(appLocale: string, preferredLocales: readonly string[] = []): string {
const appIntlLocale = toIntlLocale(appLocale)
const appLanguage = toLanguageAndScript(appIntlLocale)
if (!appLanguage) {
return appIntlLocale
}

return preferredLocales.find((tag) => toLanguageAndScript(tag) === appLanguage) ?? appIntlLocale
}

export function toDayjsLocale(appLocale: string): string {
return DAYJS_LOCALE_OVERRIDES[appLocale] ?? appLocale.split(/[-_]/)[0].toLowerCase()
}
Expand All @@ -34,7 +60,7 @@ export function resolveActiveDateLocales(options: ResolveActiveDateLocalesOption
}

return {
intlLocale: toIntlLocale(options.appLocale),
intlLocale: resolveIntlLocale(options.appLocale, options.preferredLocales),
dayjsLocale: toDayjsLocale(options.appLocale),
}
}
2 changes: 0 additions & 2 deletions packages/models/src/Domain/Syncable/UserPrefs/PrefDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ export const PrefDefaults = {
[PrefKey.AddImportsToTag]: true,
[PrefKey.AlwaysCreateNewTagForImports]: true,
[PrefKey.ExistingTagForImports]: undefined,
[PrefKey.Locale]: undefined,
[PrefKey.HasSeenLocalizationLabsSpotlight]: false,
} satisfies {
[key in PrefKey]: PrefValue[key]
}
4 changes: 0 additions & 4 deletions packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export enum PrefKey {
AddImportsToTag = 'addImportsToTag',
AlwaysCreateNewTagForImports = 'alwaysCreateNewTagForImports',
ExistingTagForImports = 'existingTagForImports',
Locale = 'locale',
HasSeenLocalizationLabsSpotlight = 'hasSeenLocalizationLabsSpotlight',
DEPRECATED_ActiveThemes = 'activeThemes',
DEPRECATED_UseSystemColorScheme = 'useSystemColorScheme',
DEPRECATED_UseTranslucentUI = 'useTranslucentUI',
Expand Down Expand Up @@ -104,7 +102,5 @@ export type PrefValue = {
[PrefKey.AddImportsToTag]: boolean
[PrefKey.AlwaysCreateNewTagForImports]: boolean
[PrefKey.ExistingTagForImports]: string | undefined
[PrefKey.Locale]: string | undefined
[PrefKey.HasSeenLocalizationLabsSpotlight]: boolean
[PrefKey.SuperNoteImageAlignment]: 'left' | 'center' | 'right'
}
6 changes: 0 additions & 6 deletions packages/services/src/Domain/Preferences/LocalPrefKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export enum LocalPrefKey {
EditorLineHeight = 'editorLineHeight',
EditorLineWidth = 'editorLineWidth',
EditorFontSize = 'editorFontSize',

HasSeenLocalizationLabsSpotlight = 'hasSeenLocalizationLabsSpotlight',
}

export type LocalPrefValue = {
Expand All @@ -31,8 +29,6 @@ export type LocalPrefValue = {
[LocalPrefKey.EditorLineHeight]: EditorLineHeight
[LocalPrefKey.EditorLineWidth]: EditorLineWidth
[LocalPrefKey.EditorFontSize]: EditorFontSize

[LocalPrefKey.HasSeenLocalizationLabsSpotlight]: boolean
}

export const LocalPrefDefaults = {
Expand All @@ -48,8 +44,6 @@ export const LocalPrefDefaults = {
[LocalPrefKey.EditorLineHeight]: EditorLineHeight.Normal,
[LocalPrefKey.EditorLineWidth]: EditorLineWidth.FullWidth,
[LocalPrefKey.EditorFontSize]: EditorFontSize.Normal,

[LocalPrefKey.HasSeenLocalizationLabsSpotlight]: false,
} satisfies {
[key in LocalPrefKey]: LocalPrefValue[key]
}
4 changes: 0 additions & 4 deletions packages/web/src/javascripts/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ import { ElementIds } from './Constants/ElementIDs'
import { setDefaultMonospaceFont } from './setDefaultMonospaceFont'
import { RouteParser, RouteType } from '@standardnotes/ui-services'
import U2FAuthIframe from './Components/U2FAuthIframe/U2FAuthIframe'
import { activatePersistedLocale } from './Controllers/Localization/WebLocaleService'

let keyCount = 0
const getKey = () => {
return keyCount++
Expand All @@ -60,8 +58,6 @@ const startApplication: StartApplication = async function startApplication(
SNLog.onError = console.error
let root: Root

await activatePersistedLocale()

const onDestroy = () => {
const rootElement = document.getElementById(ElementIds.RootId) as HTMLElement
root.unmount()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import { ApplicationEventObserver } from '@/Event/ApplicationEventObserver'
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import { LinkingController } from '@/Controllers/LinkingController'
import { LocalizationController } from '@/Controllers/Localization/LocalizationController'
import { getSavedLocaleForApplication } from '@/Utils/LocalePersistence'
import { SyncStatusController } from '@/Controllers/SyncStatusController'
import { ActionsMenuController } from '@/Controllers/ActionsMenuController'
import { ItemGroupController } from '@/Components/NoteView/Controller/ItemGroupController'
Expand Down Expand Up @@ -319,8 +318,7 @@ export class WebDependencies extends DependencyContainer {
this.bind(Web_TYPES.LocalizationController, () => {
return new LocalizationController(
this.get<FeaturesController>(Web_TYPES.FeaturesController),
() => getSavedLocaleForApplication(application),
application.preferences,
application.identifier,
this.get<PreferencesController>(Web_TYPES.PreferencesController),
application.routeService,
application.events,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WebApplication } from './WebApplication'
import { ApplicationDescriptor, SNApplicationGroup } from '@standardnotes/snjs'
import { getPlatform, isDesktopApplication } from '@/Utils'
import { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
import { activatePersistedLocale } from '@/Controllers/Localization/WebLocaleService'

const createApplication = (
descriptor: ApplicationDescriptor,
Expand Down Expand Up @@ -38,6 +39,7 @@ export class WebApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice>

await super.initialize({
applicationCreator: async (descriptor, device) => {
await activatePersistedLocale(descriptor.identifier)
return createApplication(descriptor, device, defaultSyncServerHost, device, webSocketUrl)
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const LocalizationLabsSpotlightModal = ({ application }: Props) => {
<AlertDialog closeDialog={dismiss}>
<div className="flex items-center justify-between">
<div className="flex items-start">
<Title className="mb-0">{c('B6.Preferences.General.Language.Title').t`Language Settings`}</Title>
<Title className="mb-0">{c('B6.Preferences.General.Language.Title').t`Language Preferences`}</Title>
<Pill style={'success'}>{c('B6.Preferences.General.Label').t`Labs`}</Pill>
</div>
<button
Expand All @@ -47,14 +47,14 @@ const LocalizationLabsSpotlightModal = ({ application }: Props) => {
<Text className="mt-3">
{usesOsLanguage
? c('B6.Preferences.General.Language.Spotlight.Info')
.t`You can now use the app in your preferred language. Turn on this Labs feature in Settings → General → Language. Your OS language is used when no preference is set.`
.t`You can now use the app in your preferred language. Turn on this Labs feature in Preferences → General → Language. Your OS language is used when no preference is set.`
: c('B6.Preferences.General.Language.Spotlight.Info')
.t`You can now use the app in your preferred language. Turn on this Labs feature in Settings → General → Language. Your browser language is used when no preference is set.`}
.t`You can now use the app in your preferred language. Turn on this Labs feature in Preferences → General → Language. Your browser language is used when no preference is set.`}
</Text>
<div className="mt-4 flex flex-col-reverse justify-end gap-2 sm:flex-row">
<Button onClick={dismiss}>{c('B6.Preferences.General.Language.Spotlight.ActionDismiss').t`Not now`}</Button>
<Button primary onClick={openLanguageSettings}>
{c('B6.Preferences.General.Language.Spotlight.ActionOpen').t`Open language settings`}
{c('B6.Preferences.General.Language.Spotlight.ActionOpen').t`Open language preferences`}
</Button>
</div>
</AlertDialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ const FilesSection: FunctionComponent<Props> = ({ application }) => {
</div>
) : (
<>
<div className="mb-1 mt-1">
{filesQuotaText}
</div>
<div className="mb-1 mt-1">{filesQuotaText}</div>
<progress
className="progress-bar w-full"
aria-label={c('B6.Preferences.Account.Label').t`Files storage used`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ const Language: FunctionComponent<Props> = ({ application }) => {
const switchAnimation = new Promise<void>((resolve) => {
window.setTimeout(resolve, LABS_SWITCH_ANIMATION_MS)
})
void application.localizationController.toggleLocalization(switchAnimation)
void application.localizationController.toggleLocalization(switchAnimation).then((isReloading) => {
if (!isReloading) {
isTogglingRef.current = false
setIsToggling(false)
}
})
}, [application])

const showLanguageDropdown = labsSwitchChecked && localeItems.length > 0
Expand Down Expand Up @@ -120,7 +125,7 @@ const Language: FunctionComponent<Props> = ({ application }) => {
<PreferencesSegment>
<div className="flex items-center justify-between">
<div className="flex items-start">
<Title>{c('B6.Preferences.General.Language.Title').t`Language Settings`}</Title>
<Title>{c('B6.Preferences.General.Language.Title').t`Language Preferences`}</Title>
<Pill style={'success'}>{c('B6.Preferences.General.Label').t`Labs`}</Pill>
</div>
<Switch onChange={toggle} checked={labsSwitchChecked} />
Expand Down
Loading
Loading