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
from warnings import filterwarnings
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
filterwarnings(
"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 { UpgradeController } from './UpgradeController';
import { ZoneController } from './ZoneController';
import { LocaleController } from './LocaleController';
/**
* 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: InitController, identifier: 'w-init' },
{ controllerConstructor: KeyboardController, identifier: 'w-kbd' },
{ controllerConstructor: LocaleController, identifier: 'w-locale' },
{ controllerConstructor: OrderableController, identifier: 'w-orderable' },
{ controllerConstructor: PreviewController, identifier: 'w-preview' },
{ controllerConstructor: ProgressController, identifier: 'w-progress' },

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,8 +1,7 @@
import warnings
from operator import itemgetter
import l18n
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models.fields import BLANK_CHOICE_DASH
from django.utils.translation import get_language_info
@ -59,12 +58,9 @@ def _get_language_choices():
def _get_time_zone_choices():
time_zones = [
(tz, str(l18n.tz_fullnames.get(tz, tz)))
for tz in get_available_admin_time_zones()
return [("", _("Use server time zone"))] + [
(tz, tz) for tz in get_available_admin_time_zones()
]
time_zones.sort(key=itemgetter(1))
return BLANK_CHOICE_DASH + time_zones
class LocalePreferencesForm(forms.ModelForm):
@ -82,7 +78,16 @@ class LocalePreferencesForm(forms.ModelForm):
)
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:

Wyświetl plik

@ -588,6 +588,22 @@ class TestAccountSection(
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")
@override_settings(WAGTAIL_USER_TIME_ZONES=["Europe/London"])
def test_not_show_options_if_only_one_time_zone_is_permitted(self):