Load the Intl.Segmenter and Intl.DurationFormat polyfills only if needed (#2778)

* Load the Intl.Segmenter polyfill only if needed

* Also polyfill Intl.DurationFormat only if needed

* Polyfill Intl.* in tests

* Load the default translations in tests

* Instanciate the Intl.DurationFormat in the component
This commit is contained in:
Quentin Gliech 2024-11-14 19:06:38 +01:00 committed by GitHub
parent 6e5c468780
commit 137a53dbee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 60 additions and 31 deletions

View File

@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details.
*/ */
import "matrix-js-sdk/src/@types/global"; import "matrix-js-sdk/src/@types/global";
import type { DurationFormat as PolyfillDurationFormat } from "@formatjs/intl-durationformat";
import { Controls } from "../controls"; import { Controls } from "../controls";
declare global { declare global {
@ -23,4 +24,9 @@ declare global {
// Safari only supports this prefixed, so tell the type system about it // Safari only supports this prefixed, so tell the type system about it
webkitRequestFullscreen: () => void; webkitRequestFullscreen: () => void;
} }
namespace Intl {
// Add DurationFormat as part of the Intl namespace because we polyfill it
const DurationFormat: typeof PolyfillDurationFormat;
}
} }

View File

@ -9,9 +9,9 @@ import { expect, test } from "vitest";
import { Initializer } from "../src/initializer"; import { Initializer } from "../src/initializer";
test("initBeforeReact sets font family from URL param", () => { test("initBeforeReact sets font family from URL param", async () => {
window.location.hash = "#?font=DejaVu Sans"; window.location.hash = "#?font=DejaVu Sans";
Initializer.initBeforeReact(); await Initializer.initBeforeReact();
expect( expect(
getComputedStyle(document.documentElement).getPropertyValue( getComputedStyle(document.documentElement).getPropertyValue(
"--font-family", "--font-family",
@ -19,9 +19,9 @@ test("initBeforeReact sets font family from URL param", () => {
).toBe('"DejaVu Sans"'); ).toBe('"DejaVu Sans"');
}); });
test("initBeforeReact sets font scale from URL param", () => { test("initBeforeReact sets font scale from URL param", async () => {
window.location.hash = "#?fontScale=1.2"; window.location.hash = "#?fontScale=1.2";
Initializer.initBeforeReact(); await Initializer.initBeforeReact();
expect( expect(
getComputedStyle(document.documentElement).getPropertyValue("--font-scale"), getComputedStyle(document.documentElement).getPropertyValue("--font-scale"),
).toBe("1.2"); ).toBe("1.2");

View File

@ -11,6 +11,8 @@ import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend"; import Backend from "i18next-http-backend";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
import { shouldPolyfill as shouldPolyfillDurationFormat } from "@formatjs/intl-durationformat/should-polyfill";
import { getUrlParams } from "./UrlParams"; import { getUrlParams } from "./UrlParams";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
@ -41,10 +43,17 @@ export class Initializer {
return Initializer.internalInstance?.isInitialized; return Initializer.internalInstance?.isInitialized;
} }
public static initBeforeReact(): void { public static async initBeforeReact(): Promise<void> {
// this maybe also needs to return a promise in the future, const polyfills: Promise<unknown>[] = [];
// if we have to do async inits before showing the loading screen if (shouldPolyfillSegmenter()) {
// but this should be avoided if possible polyfills.push(import("@formatjs/intl-segmenter/polyfill-force"));
}
if (shouldPolyfillDurationFormat()) {
polyfills.push(import("@formatjs/intl-durationformat/polyfill-force"));
}
await Promise.all(polyfills);
//i18n //i18n
const languageDetector = new LanguageDetector(); const languageDetector = new LanguageDetector();
@ -54,7 +63,7 @@ export class Initializer {
lookup: () => getUrlParams().lang ?? undefined, lookup: () => getUrlParams().lang ?? undefined,
}); });
i18n await i18n
.use(Backend) .use(Backend)
.use(languageDetector) .use(languageDetector)
.use(initReactI18next) .use(initReactI18next)
@ -74,9 +83,6 @@ export class Initializer {
order: ["urlFragment", "navigator"], order: ["urlFragment", "navigator"],
caches: [], caches: [],
}, },
})
.catch((e) => {
logger.error("Failed to initialize i18n", e);
}); });
// Custom Themeing // Custom Themeing

View File

@ -20,8 +20,6 @@ import {
setLogExtension as setLKLogExtension, setLogExtension as setLKLogExtension,
setLogLevel as setLKLogLevel, setLogLevel as setLKLogLevel,
} from "livekit-client"; } from "livekit-client";
import "@formatjs/intl-segmenter/polyfill";
import "@formatjs/intl-durationformat/polyfill";
import { App } from "./App"; import { App } from "./App";
import { init as initRageshake } from "./settings/rageshake"; import { init as initRageshake } from "./settings/rageshake";
@ -57,12 +55,17 @@ if (fatalError !== null) {
throw fatalError; // Stop the app early throw fatalError; // Stop the app early
} }
Initializer.initBeforeReact(); Initializer.initBeforeReact()
.then(() => {
const history = createBrowserHistory();
const history = createBrowserHistory(); root.render(
root.render(
<StrictMode> <StrictMode>
<App history={history} /> <App history={history} />
</StrictMode>, </StrictMode>,
); );
})
.catch((e) => {
logger.error("Failed to initialize app", e);
root.render(e.message);
});

View File

@ -11,19 +11,12 @@ import {
useCallback, useCallback,
useEffect, useEffect,
useState, useState,
useMemo,
} from "react"; } from "react";
import { DurationFormat } from "@formatjs/intl-durationformat";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ReactionIndicator } from "./ReactionIndicator"; import { ReactionIndicator } from "./ReactionIndicator";
const durationFormatter = new DurationFormat(undefined, {
minutesDisplay: "always",
secondsDisplay: "always",
hoursDisplay: "auto",
style: "digital",
});
export function RaisedHandIndicator({ export function RaisedHandIndicator({
raisedHandTime, raisedHandTime,
miniature, miniature,
@ -38,6 +31,17 @@ export function RaisedHandIndicator({
const { t } = useTranslation(); const { t } = useTranslation();
const [raisedHandDuration, setRaisedHandDuration] = useState(""); const [raisedHandDuration, setRaisedHandDuration] = useState("");
const durationFormatter = useMemo(
() =>
new Intl.DurationFormat(undefined, {
minutesDisplay: "always",
secondsDisplay: "always",
hoursDisplay: "auto",
style: "digital",
}),
[],
);
const clickCallback = useCallback<MouseEventHandler<HTMLButtonElement>>( const clickCallback = useCallback<MouseEventHandler<HTMLButtonElement>>(
(event) => { (event) => {
if (!onClick) { if (!onClick) {
@ -69,7 +73,7 @@ export function RaisedHandIndicator({
calculateTime(); calculateTime();
const to = setInterval(calculateTime, 1000); const to = setInterval(calculateTime, 1000);
return (): void => clearInterval(to); return (): void => clearInterval(to);
}, [setRaisedHandDuration, raisedHandTime, showTimer]); }, [setRaisedHandDuration, raisedHandTime, showTimer, durationFormatter]);
if (!raisedHandTime) { if (!raisedHandTime) {
return; return;

View File

@ -6,6 +6,8 @@ Please see LICENSE in the repository root for full details.
*/ */
import "global-jsdom/register"; import "global-jsdom/register";
import "@formatjs/intl-durationformat/polyfill";
import "@formatjs/intl-segmenter/polyfill";
import i18n from "i18next"; import i18n from "i18next";
import posthog from "posthog-js"; import posthog from "posthog-js";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
@ -14,6 +16,7 @@ import { cleanup } from "@testing-library/react";
import "vitest-axe/extend-expect"; import "vitest-axe/extend-expect";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import EN_GB from "../public/locales/en-GB/app.json";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
// Bare-minimum i18n config // Bare-minimum i18n config
@ -22,6 +25,13 @@ i18n
.init({ .init({
lng: "en-GB", lng: "en-GB",
fallbackLng: "en-GB", fallbackLng: "en-GB",
supportedLngs: ["en-GB"],
// We embed the translations, so that it never needs to fetch
resources: {
"en-GB": {
app: EN_GB,
},
},
interpolation: { interpolation: {
escapeValue: false, // React has built-in XSS protections escapeValue: false, // React has built-in XSS protections
}, },