Replace l18n library with Intl-based LocaleController

pull/12730/merge
Sage Abdullah 2025-01-03 17:49:25 +00:00 zatwierdzone przez Thibaud Colas
rodzic 983a86b0b8
commit 944fd02dc3
9 zmienionych plików z 862 dodań i 15 usunięć

Wyświetl plik

@ -167,10 +167,6 @@ jobs:
tee -a testproject/settings/local.py << EOF tee -a testproject/settings/local.py << EOF
from warnings import filterwarnings from warnings import filterwarnings
SILENCED_SYSTEM_CHECKS = ["wagtailadmin.W001"] SILENCED_SYSTEM_CHECKS = ["wagtailadmin.W001"]
# Remove when l18n dependency is updated or removed
filterwarnings(
"ignore", "'locale.getdefaultlocale' is deprecated .*"
)
# Remove when all experimental jobs use Django >= 6.0 # Remove when all experimental jobs use Django >= 6.0
filterwarnings( filterwarnings(
"ignore", "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated." "ignore", "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated."

Wyświetl plik

@ -0,0 +1,137 @@
import { Application } from '@hotwired/stimulus';
import { LocaleController } from './LocaleController';
import { InitController } from './InitController';
describe('LocaleController', () => {
let app;
let select;
const setup = async (html) => {
document.body.innerHTML = `<main>${html}</main>`;
select = document.querySelector('select');
select.innerHTML = /* html */ `
<option value="" selected>Use server time zone</option>
<option value="Africa/Abidjan">Africa/Abidjan</option>
<option value="America/Argentina/Jujuy">America/Argentina/Jujuy</option>
<option value="America/Indiana/Knox">America/Indiana/Knox</option>
<option value="Antarctica/Rothera">Antarctica/Rothera</option>
<option value="Arctic/Longyearbyen">Arctic/Longyearbyen</option>
<option value="Asia/Katmandu">Asia/Katmandu</option>
<option value="Atlantic/Canary">Atlantic/Canary</option>
<option value="Australia/South">Australia/South</option>
<option value="Brazil/East">Brazil/East</option>
<option value="Canada/Atlantic">Canada/Atlantic</option>
<option value="Chile/Continental">Chile/Continental</option>
<option value="EST">EST</option>
<option value="Etc/GMT-7">Etc/GMT-7</option>
<option value="Europe/Brussels">Europe/Brussels</option>
<option value="GMT">GMT</option>
<option value="Indian/Maldives">Indian/Maldives</option>
<option value="Pacific/Tarawa">Pacific/Tarawa</option>
<option value="UTC">UTC</option>
<option value="Universal">Universal</option>
<option value="Zulu">Zulu</option>
`;
app = Application.start();
app.register('w-locale', LocaleController);
app.register('w-init', InitController);
await Promise.resolve();
};
afterEach(() => {
app?.stop();
jest.clearAllMocks();
});
describe('localizing time zone options', () => {
it('should append localized time zone labels to the options', async () => {
document.documentElement.lang = 'en-US';
await setup(/* html */ `
<select
name="locale-current_time_zone"
data-controller="w-init w-locale"
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
data-w-locale-server-time-zone-param="Europe/London"
>
</select>
`);
expect(select.getAttribute('data-controller')).toEqual('w-locale');
const selected = select.selectedOptions[0];
expect(selected).toBeTruthy();
expect(selected.value).toEqual('');
expect(selected.textContent).toEqual(
'Use server time zone: GMT (Greenwich Mean Time)',
);
expect(select).toMatchSnapshot();
});
});
it('should localize to the current HTML locale and use the server time zone param for the default', async () => {
document.documentElement.lang = 'id-ID';
await setup(/* html */ `
<select
name="locale-current_time_zone"
data-controller="w-init w-locale"
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
data-w-locale-server-time-zone-param="Asia/Jakarta"
>
</select>
`);
expect(select.getAttribute('data-controller')).toEqual('w-locale');
const selected = select.selectedOptions[0];
expect(selected).toBeTruthy();
expect(selected.value).toEqual('');
expect(selected.textContent).toEqual(
'Use server time zone: WIB (Waktu Indonesia Barat)',
);
expect(select).toMatchSnapshot();
});
it('should skip updating the default option if server time zone is not provided', async () => {
document.documentElement.lang = 'ar';
await setup(/* html */ `
<select
name="locale-current_time_zone"
data-controller="w-init w-locale"
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
>
</select>
`);
expect(select.getAttribute('data-controller')).toEqual('w-locale');
const selected = select.selectedOptions[0];
expect(selected).toBeTruthy();
expect(selected.value).toEqual('');
expect(selected.textContent).toEqual('Use server time zone');
expect(select).toMatchSnapshot();
});
it('should allow updating the time zone options on an uncontrolled select element via events', async () => {
document.documentElement.lang = 'id-ID';
await setup(/* html */ `
<form data-controller="w-locale">
<select
name="locale-current_time_zone"
data-action="custom:event->w-locale#localizeTimeZoneOptions"
data-w-locale-server-time-zone-param="Asia/Tokyo"
>
</select>
</form>
`);
select.dispatchEvent(new CustomEvent('custom:event'));
await Promise.resolve();
expect(select.hasAttribute('data-controller')).toBe(false);
const selected = select.selectedOptions[0];
expect(selected).toBeTruthy();
expect(selected.value).toEqual('');
expect(selected.textContent).toEqual(
'Use server time zone: GMT+9 (Waktu Standar Jepang)',
);
expect(select).toMatchSnapshot();
});
});

Wyświetl plik

@ -0,0 +1,55 @@
import { Controller } from '@hotwired/stimulus';
/**
* Localizes elements in the current locale.
*/
export class LocaleController extends Controller<HTMLSelectElement> {
/**
* Localize an IANA time zone in the current locale.
*
* @param timeZone An IANA time zone string
* @param format Time zone name formatting option
* @returns formatted time zone name in the current locale
*/
static localizeTimeZone(
timeZone: string,
format: Intl.DateTimeFormatOptions['timeZoneName'],
) {
const df = new Intl.DateTimeFormat(document.documentElement.lang, {
timeZone,
timeZoneName: format,
});
const parts = df.formatToParts(new Date());
return parts.find((part) => part.type === 'timeZoneName')!.value;
}
/**
*
* @param timeZone An IANA time zone string
* @returns formatted time zone name in the current locale with short and long
* labels, e.g. `"GMT+7 (Western Indonesia Time)"`
*/
static getTZLabel(timeZone: string) {
const shortLabel = LocaleController.localizeTimeZone(timeZone, 'short');
const longLabel = LocaleController.localizeTimeZone(timeZone, 'long');
return `${shortLabel} (${longLabel})`;
}
/**
* Localize the time zone `<options>` of a `<select>` element in the current
* locale.
*/
localizeTimeZoneOptions(
event?: Event & { params?: { serverTimeZone?: string } },
) {
const element = (event?.target as HTMLSelectElement) || this.element;
const serverTimeZone = event?.params?.serverTimeZone;
Array.from(element.options).forEach((opt) => {
const timeZone = opt.value || serverTimeZone;
if (!timeZone) return;
const localized = LocaleController.getTZLabel(timeZone);
const option = opt;
option.textContent = `${option.textContent}: ${localized}`;
});
}
}

Wyświetl plik

@ -0,0 +1,639 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LocaleController localizing time zone options should append localized time zone labels to the options 1`] = `
<select
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
data-controller="w-locale"
data-w-locale-server-time-zone-param="Europe/London"
name="locale-current_time_zone"
>
<option
selected=""
value=""
>
Use server time zone: GMT (Greenwich Mean Time)
</option>
<option
value="Africa/Abidjan"
>
Africa/Abidjan: GMT (Greenwich Mean Time)
</option>
<option
value="America/Argentina/Jujuy"
>
America/Argentina/Jujuy: GMT-3 (Argentina Standard Time)
</option>
<option
value="America/Indiana/Knox"
>
America/Indiana/Knox: CST (Central Standard Time)
</option>
<option
value="Antarctica/Rothera"
>
Antarctica/Rothera: GMT-3 (Rothera Time)
</option>
<option
value="Arctic/Longyearbyen"
>
Arctic/Longyearbyen: GMT+1 (Central European Standard Time)
</option>
<option
value="Asia/Katmandu"
>
Asia/Katmandu: GMT+5:45 (Nepal Time)
</option>
<option
value="Atlantic/Canary"
>
Atlantic/Canary: GMT (Western European Standard Time)
</option>
<option
value="Australia/South"
>
Australia/South: GMT+10:30 (Australian Central Daylight Time)
</option>
<option
value="Brazil/East"
>
Brazil/East: GMT-3 (Brasilia Standard Time)
</option>
<option
value="Canada/Atlantic"
>
Canada/Atlantic: AST (Atlantic Standard Time)
</option>
<option
value="Chile/Continental"
>
Chile/Continental: GMT-3 (Chile Summer Time)
</option>
<option
value="EST"
>
EST: EST (Eastern Standard Time)
</option>
<option
value="Etc/GMT-7"
>
Etc/GMT-7: GMT+7 (GMT+07:00)
</option>
<option
value="Europe/Brussels"
>
Europe/Brussels: GMT+1 (Central European Standard Time)
</option>
<option
value="GMT"
>
GMT: UTC (Coordinated Universal Time)
</option>
<option
value="Indian/Maldives"
>
Indian/Maldives: GMT+5 (Maldives Time)
</option>
<option
value="Pacific/Tarawa"
>
Pacific/Tarawa: GMT+12 (Gilbert Islands Time)
</option>
<option
value="UTC"
>
UTC: UTC (Coordinated Universal Time)
</option>
<option
value="Universal"
>
Universal: UTC (Coordinated Universal Time)
</option>
<option
value="Zulu"
>
Zulu: UTC (Coordinated Universal Time)
</option>
</select>
`;
exports[`LocaleController should allow updating the time zone options on an uncontrolled select element via events 1`] = `
<select
data-action="custom:event->w-locale#localizeTimeZoneOptions"
data-w-locale-server-time-zone-param="Asia/Tokyo"
name="locale-current_time_zone"
>
<option
selected=""
value=""
>
Use server time zone: GMT+9 (Waktu Standar Jepang)
</option>
<option
value="Africa/Abidjan"
>
Africa/Abidjan: GMT (Greenwich Mean Time)
</option>
<option
value="America/Argentina/Jujuy"
>
America/Argentina/Jujuy: GMT-3 (Waktu Standar Argentina)
</option>
<option
value="America/Indiana/Knox"
>
America/Indiana/Knox: CST (Waktu Standar Tengah)
</option>
<option
value="Antarctica/Rothera"
>
Antarctica/Rothera: GMT-3 (Waktu Rothera)
</option>
<option
value="Arctic/Longyearbyen"
>
Arctic/Longyearbyen: GMT+1 (Waktu Standar Eropa Tengah)
</option>
<option
value="Asia/Katmandu"
>
Asia/Katmandu: GMT+5.45 (Waktu Nepal)
</option>
<option
value="Atlantic/Canary"
>
Atlantic/Canary: GMT (Waktu Standar Eropa Barat)
</option>
<option
value="Australia/South"
>
Australia/South: GMT+10.30 (Waktu Musim Panas Tengah Australia)
</option>
<option
value="Brazil/East"
>
Brazil/East: GMT-3 (Waktu Standar Brasil)
</option>
<option
value="Canada/Atlantic"
>
Canada/Atlantic: AST (Waktu Standar Atlantik)
</option>
<option
value="Chile/Continental"
>
Chile/Continental: GMT-3 (Waktu Musim Panas Cile)
</option>
<option
value="EST"
>
EST: EST (Waktu Standar Timur)
</option>
<option
value="Etc/GMT-7"
>
Etc/GMT-7: GMT+7 (GMT+07.00)
</option>
<option
value="Europe/Brussels"
>
Europe/Brussels: GMT+1 (Waktu Standar Eropa Tengah)
</option>
<option
value="GMT"
>
GMT: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Indian/Maldives"
>
Indian/Maldives: GMT+5 (Waktu Maladewa)
</option>
<option
value="Pacific/Tarawa"
>
Pacific/Tarawa: GMT+12 (Waktu Kep. Gilbert)
</option>
<option
value="UTC"
>
UTC: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Universal"
>
Universal: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Zulu"
>
Zulu: UTC (Waktu Universal Terkoordinasi)
</option>
</select>
`;
exports[`LocaleController should localize to the current HTML locale and use the server time zone param for the default 1`] = `
<select
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
data-controller="w-locale"
data-w-locale-server-time-zone-param="Asia/Jakarta"
name="locale-current_time_zone"
>
<option
selected=""
value=""
>
Use server time zone: WIB (Waktu Indonesia Barat)
</option>
<option
value="Africa/Abidjan"
>
Africa/Abidjan: GMT (Greenwich Mean Time)
</option>
<option
value="America/Argentina/Jujuy"
>
America/Argentina/Jujuy: GMT-3 (Waktu Standar Argentina)
</option>
<option
value="America/Indiana/Knox"
>
America/Indiana/Knox: CST (Waktu Standar Tengah)
</option>
<option
value="Antarctica/Rothera"
>
Antarctica/Rothera: GMT-3 (Waktu Rothera)
</option>
<option
value="Arctic/Longyearbyen"
>
Arctic/Longyearbyen: GMT+1 (Waktu Standar Eropa Tengah)
</option>
<option
value="Asia/Katmandu"
>
Asia/Katmandu: GMT+5.45 (Waktu Nepal)
</option>
<option
value="Atlantic/Canary"
>
Atlantic/Canary: GMT (Waktu Standar Eropa Barat)
</option>
<option
value="Australia/South"
>
Australia/South: GMT+10.30 (Waktu Musim Panas Tengah Australia)
</option>
<option
value="Brazil/East"
>
Brazil/East: GMT-3 (Waktu Standar Brasil)
</option>
<option
value="Canada/Atlantic"
>
Canada/Atlantic: AST (Waktu Standar Atlantik)
</option>
<option
value="Chile/Continental"
>
Chile/Continental: GMT-3 (Waktu Musim Panas Cile)
</option>
<option
value="EST"
>
EST: EST (Waktu Standar Timur)
</option>
<option
value="Etc/GMT-7"
>
Etc/GMT-7: GMT+7 (GMT+07.00)
</option>
<option
value="Europe/Brussels"
>
Europe/Brussels: GMT+1 (Waktu Standar Eropa Tengah)
</option>
<option
value="GMT"
>
GMT: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Indian/Maldives"
>
Indian/Maldives: GMT+5 (Waktu Maladewa)
</option>
<option
value="Pacific/Tarawa"
>
Pacific/Tarawa: GMT+12 (Waktu Kep. Gilbert)
</option>
<option
value="UTC"
>
UTC: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Universal"
>
Universal: UTC (Waktu Universal Terkoordinasi)
</option>
<option
value="Zulu"
>
Zulu: UTC (Waktu Universal Terkoordinasi)
</option>
</select>
`;
exports[`LocaleController should skip updating the default option if server time zone is not provided 1`] = `
<select
data-action="w-init:ready->w-locale#localizeTimeZoneOptions"
data-controller="w-locale"
name="locale-current_time_zone"
>
<option
selected=""
value=""
>
Use server time zone
</option>
<option
value="Africa/Abidjan"
>
Africa/Abidjan: غرينتش (توقيت غرينتش)
</option>
<option
value="America/Argentina/Jujuy"
>
America/Argentina/Jujuy: غرينتش-٣ (توقيت الأرجنتين الرسمي)
</option>
<option
value="America/Indiana/Knox"
>
America/Indiana/Knox: غرينتش-٦ (التوقيت الرسمي المركزي لأمريكا الشمالية)
</option>
<option
value="Antarctica/Rothera"
>
Antarctica/Rothera: غرينتش-٣ (توقيت روثيرا)
</option>
<option
value="Arctic/Longyearbyen"
>
Arctic/Longyearbyen: غرينتش+١ (توقيت وسط أوروبا الرسمي)
</option>
<option
value="Asia/Katmandu"
>
Asia/Katmandu: غرينتش+٥:٤٥ (توقيت نيبال)
</option>
<option
value="Atlantic/Canary"
>
Atlantic/Canary: غرينتش (توقيت غرب أوروبا الرسمي)
</option>
<option
value="Australia/South"
>
Australia/South: غرينتش+١٠:٣٠ (توقيت وسط أستراليا الصيفي)
</option>
<option
value="Brazil/East"
>
Brazil/East: غرينتش-٣ (توقيت برازيليا الرسمي)
</option>
<option
value="Canada/Atlantic"
>
Canada/Atlantic: غرينتش-٤ (التوقيت الرسمي الأطلسي)
</option>
<option
value="Chile/Continental"
>
Chile/Continental: غرينتش-٣ (توقيت تشيلي الصيفي)
</option>
<option
value="EST"
>
EST: غرينتش-٥ (التوقيت الرسمي الشرقي لأمريكا الشمالية)
</option>
<option
value="Etc/GMT-7"
>
Etc/GMT-7: غرينتش+٧ (غرينتش+٠٧:٠٠)
</option>
<option
value="Europe/Brussels"
>
Europe/Brussels: غرينتش+١ (توقيت وسط أوروبا الرسمي)
</option>
<option
value="GMT"
>
GMT: UTC (التوقيت العالمي المنسق)
</option>
<option
value="Indian/Maldives"
>
Indian/Maldives: غرينتش+٥ (توقيت جزر المالديف)
</option>
<option
value="Pacific/Tarawa"
>
Pacific/Tarawa: غرينتش+١٢ (توقيت جزر جيلبرت)
</option>
<option
value="UTC"
>
UTC: UTC (التوقيت العالمي المنسق)
</option>
<option
value="Universal"
>
Universal: UTC (التوقيت العالمي المنسق)
</option>
<option
value="Zulu"
>
Zulu: UTC (التوقيت العالمي المنسق)
</option>
</select>
`;

Wyświetl plik

@ -32,6 +32,7 @@ import { TooltipController } from './TooltipController';
import { UnsavedController } from './UnsavedController'; import { UnsavedController } from './UnsavedController';
import { UpgradeController } from './UpgradeController'; import { UpgradeController } from './UpgradeController';
import { ZoneController } from './ZoneController'; import { ZoneController } from './ZoneController';
import { LocaleController } from './LocaleController';
/** /**
* Important: Only add default core controllers that should load with the base admin JS bundle. * Important: Only add default core controllers that should load with the base admin JS bundle.
@ -53,6 +54,7 @@ export const coreControllerDefinitions: Definition[] = [
{ controllerConstructor: FormsetController, identifier: 'w-formset' }, { controllerConstructor: FormsetController, identifier: 'w-formset' },
{ controllerConstructor: InitController, identifier: 'w-init' }, { controllerConstructor: InitController, identifier: 'w-init' },
{ controllerConstructor: KeyboardController, identifier: 'w-kbd' }, { controllerConstructor: KeyboardController, identifier: 'w-kbd' },
{ controllerConstructor: LocaleController, identifier: 'w-locale' },
{ controllerConstructor: OrderableController, identifier: 'w-orderable' }, { controllerConstructor: OrderableController, identifier: 'w-orderable' },
{ controllerConstructor: PreviewController, identifier: 'w-preview' }, { controllerConstructor: PreviewController, identifier: 'w-preview' },
{ controllerConstructor: ProgressController, identifier: 'w-progress' }, { controllerConstructor: ProgressController, identifier: 'w-progress' },

Wyświetl plik

@ -31,7 +31,6 @@ install_requires = [
"beautifulsoup4>=4.8,<4.13", "beautifulsoup4>=4.8,<4.13",
"Willow[heif]>=1.8.0,<2", "Willow[heif]>=1.8.0,<2",
"requests>=2.11.1,<3.0", "requests>=2.11.1,<3.0",
"l18n>=2018.5",
"openpyxl>=3.0.10,<4.0", "openpyxl>=3.0.10,<4.0",
"anyascii>=0.1.5", "anyascii>=0.1.5",
"telepath>=0.3.1,<1", "telepath>=0.3.1,<1",

Wyświetl plik

@ -1,7 +1,6 @@
import types import types
from functools import wraps from functools import wraps
import l18n
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect from django.shortcuts import redirect
@ -141,7 +140,6 @@ def require_admin_access(view_func):
preferred_language = ( preferred_language = (
user.wagtail_userprofile.get_preferred_language() user.wagtail_userprofile.get_preferred_language()
) )
l18n.set_language(preferred_language)
time_zone = user.wagtail_userprofile.get_current_time_zone() time_zone = user.wagtail_userprofile.get_current_time_zone()
else: else:
time_zone = settings.TIME_ZONE time_zone = settings.TIME_ZONE

Wyświetl plik

@ -1,8 +1,7 @@
import warnings import warnings
from operator import itemgetter
import l18n
from django import forms from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db.models.fields import BLANK_CHOICE_DASH from django.db.models.fields import BLANK_CHOICE_DASH
from django.utils.translation import get_language_info from django.utils.translation import get_language_info
@ -59,12 +58,9 @@ def _get_language_choices():
def _get_time_zone_choices(): def _get_time_zone_choices():
time_zones = [ return [("", _("Use server time zone"))] + [
(tz, str(l18n.tz_fullnames.get(tz, tz))) (tz, tz) for tz in get_available_admin_time_zones()
for tz in get_available_admin_time_zones()
] ]
time_zones.sort(key=itemgetter(1))
return BLANK_CHOICE_DASH + time_zones
class LocalePreferencesForm(forms.ModelForm): class LocalePreferencesForm(forms.ModelForm):
@ -82,7 +78,16 @@ class LocalePreferencesForm(forms.ModelForm):
) )
current_time_zone = forms.ChoiceField( current_time_zone = forms.ChoiceField(
required=False, choices=_get_time_zone_choices, label=_("Current time zone") required=False,
choices=_get_time_zone_choices,
label=_("Current time zone"),
widget=forms.Select(
attrs={
"data-controller": "w-init w-locale",
"data-action": "w-init:ready->w-locale#localizeTimeZoneOptions",
"data-w-locale-server-time-zone-param": settings.TIME_ZONE,
},
),
) )
class Meta: class Meta:

Wyświetl plik

@ -588,6 +588,22 @@ class TestAccountSection(
sorted(zoneinfo.available_timezones()), sorted(zoneinfo.available_timezones()),
) )
response = self.client.get(reverse("wagtailadmin_account"))
self.assertEqual(response.status_code, 200)
soup = self.get_soup(response.content)
select = soup.select_one('select[name="locale-current_time_zone"]')
self.assertIsNotNone(select)
self.assertEqual(select.get("data-controller"), "w-init w-locale")
self.assertEqual(
select.get("data-action"),
"w-init:ready->w-locale#localizeTimeZoneOptions",
)
self.assertEqual(
select.get("data-w-locale-server-time-zone-param"),
settings.TIME_ZONE,
)
@unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled") @unittest.skipUnless(settings.USE_TZ, "Timezone support is disabled")
@override_settings(WAGTAIL_USER_TIME_ZONES=["Europe/London"]) @override_settings(WAGTAIL_USER_TIME_ZONES=["Europe/London"])
def test_not_show_options_if_only_one_time_zone_is_permitted(self): def test_not_show_options_if_only_one_time_zone_is_permitted(self):