I18n: Add utility for locale-aware duration formatting (#89351)

* I18n: Add utility for locale-aware duration formatting

* fix message

* add fallback
This commit is contained in:
Josh Hunt
2024-06-19 10:37:20 +01:00
committed by GitHub
parent b43411631b
commit db78604bd4
7 changed files with 88 additions and 11 deletions
+1
View File
@@ -244,6 +244,7 @@
"@emotion/react": "11.11.4",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@floating-ui/react": "0.26.16",
"@formatjs/intl-durationformat": "^0.2.4",
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/aws-sdk": "0.3.3",
"@grafana/azure-sdk": "0.0.3",
@@ -0,0 +1,20 @@
import '@formatjs/intl-durationformat/polyfill';
import { getI18next } from './index';
export function formatDate(value: number | Date | string, format: Intl.DateTimeFormatOptions = {}): string {
if (typeof value === 'string') {
return formatDate(new Date(value), format);
}
const i18n = getI18next();
const dateFormatter = new Intl.DateTimeFormat(i18n.language, format);
return dateFormatter.format(value);
}
export function formatDuration(duration: Intl.DurationInput, options: Intl.DurationFormatOptions = {}) {
const i18n = getI18next();
const dateFormatter = new Intl.DurationFormat(i18n.language, options);
return dateFormatter.format(duration);
}
+18 -7
View File
@@ -7,6 +7,7 @@ import { DEFAULT_LANGUAGE, NAMESPACES, VALID_LANGUAGES } from './constants';
import { loadTranslations } from './loadTranslations';
let tFunc: TFunction<string[], undefined> | undefined;
let i18nInstance: typeof i18n;
export async function initializeI18n(language: string): Promise<{ language: string | undefined }> {
// This is a placeholder so we can put a 'comment' in the message json files.
@@ -28,7 +29,7 @@ export async function initializeI18n(language: string): Promise<{ language: stri
ns: NAMESPACES,
};
let i18nInstance = i18n;
i18nInstance = i18n;
if (language === 'detect') {
i18nInstance = i18nInstance.use(LanguageDetector);
const detection: DetectorOptions = { order: ['navigator'], caches: [] };
@@ -79,10 +80,20 @@ export const t = (id: string, defaultMessage: string, values?: Record<string, un
return tFunc(id, defaultMessage, values);
};
export const i18nDate = (value: number | Date | string, format: Intl.DateTimeFormatOptions = {}): string => {
if (typeof value === 'string') {
return i18nDate(new Date(value), format);
export function getI18next() {
if (!tFunc) {
if (process.env.NODE_ENV !== 'test') {
console.warn(
'An attempt to internationalize was made before it was initialized. This was probably caused by calling a locale-aware function in the root module scope, instead of in render'
);
}
if (process.env.NODE_ENV === 'development') {
throw new Error('getI18next was called before i18n was initialized');
}
return i18n;
}
const dateFormatter = new Intl.DateTimeFormat(i18n.language, format);
return dateFormatter.format(value);
};
return i18nInstance || i18n;
}
+13
View File
@@ -0,0 +1,13 @@
import {
DurationFormatConstructor,
DurationFormatOptions as _DurationFormatOptions,
DurationInput as _DurationInput,
} from '@formatjs/intl-durationformat/src/types';
declare global {
namespace Intl {
const DurationFormat: DurationFormatConstructor;
type DurationFormatOptions = _DurationFormatOptions;
type DurationInput = _DurationInput;
}
}
+2 -2
View File
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
import { ConfirmButton, ConfirmModal, Button, Stack } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { i18nDate } from 'app/core/internationalization';
import { formatDate } from 'app/core/internationalization/dates';
import { AccessControlAction, UserSession } from 'app/types';
interface Props {
@@ -68,7 +68,7 @@ class BaseUserSessions extends PureComponent<Props, State> {
sessions.map((session, index) => (
<tr key={`${session.id}-${index}`}>
<td>{session.isActive ? 'Now' : session.seenAt}</td>
<td>{i18nDate(session.createdAt, { dateStyle: 'long' })}</td>
<td>{formatDate(session.createdAt, { dateStyle: 'long' })}</td>
<td>{session.clientIp}</td>
<td>{`${session.browser} on ${session.os} ${session.osVersion}`}</td>
<td>
+3 -2
View File
@@ -4,7 +4,8 @@ import React, { PureComponent } from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Button, Icon, LoadingPlaceholder } from '@grafana/ui';
import { i18nDate, Trans } from 'app/core/internationalization';
import { Trans } from 'app/core/internationalization';
import { formatDate } from 'app/core/internationalization/dates';
import { UserSession } from 'app/types';
interface Props {
@@ -50,7 +51,7 @@ class UserSessions extends PureComponent<Props> {
{sessions.map((session: UserSession, index) => (
<tr key={index}>
{session.isActive ? <td>Now</td> : <td>{session.seenAt}</td>}
<td>{i18nDate(session.createdAt, { dateStyle: 'long' })}</td>
<td>{formatDate(session.createdAt, { dateStyle: 'long' })}</td>
<td>{session.clientIp}</td>
<td>
{session.browser} on {session.os} {session.osVersion}
+31
View File
@@ -2503,6 +2503,16 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/ecma402-abstract@npm:2.0.0":
version: 2.0.0
resolution: "@formatjs/ecma402-abstract@npm:2.0.0"
dependencies:
"@formatjs/intl-localematcher": "npm:0.5.4"
tslib: "npm:^2.4.0"
checksum: 10/41543ba509ea3c7d6530d57b888115f7ca242f13462a951fae4d1d1f28bae10c999f4dea28a71d2f08366d4889a3f5276cae3a16c6f6417b841a84fd314c2234
languageName: node
linkType: hard
"@formatjs/fast-memoize@npm:1.2.6":
version: 1.2.6
resolution: "@formatjs/fast-memoize@npm:1.2.6"
@@ -2533,6 +2543,17 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/intl-durationformat@npm:^0.2.4":
version: 0.2.4
resolution: "@formatjs/intl-durationformat@npm:0.2.4"
dependencies:
"@formatjs/ecma402-abstract": "npm:2.0.0"
"@formatjs/intl-localematcher": "npm:0.5.4"
tslib: "npm:^2.4.0"
checksum: 10/5f500409a20d18967e17ffbc222f9b4c4bf7ef08cce20023c33f06d1989c2bc4cf700d1dd1d048748d0a36c882109d5375896a4964d6700f73ec18914c6de4ba
languageName: node
linkType: hard
"@formatjs/intl-localematcher@npm:0.2.31":
version: 0.2.31
resolution: "@formatjs/intl-localematcher@npm:0.2.31"
@@ -2542,6 +2563,15 @@ __metadata:
languageName: node
linkType: hard
"@formatjs/intl-localematcher@npm:0.5.4":
version: 0.5.4
resolution: "@formatjs/intl-localematcher@npm:0.5.4"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10/780cb29b42e1ea87f2eb5db268577fcdc53da52d9f096871f3a1bb78603b4ba81d208ea0b0b9bc21548797c941ce435321f62d2522795b83b740f90b0ceb5778
languageName: node
linkType: hard
"@gar/promisify@npm:^1.0.1":
version: 1.1.3
resolution: "@gar/promisify@npm:1.1.3"
@@ -17036,6 +17066,7 @@ __metadata:
"@emotion/react": "npm:11.11.4"
"@fingerprintjs/fingerprintjs": "npm:^3.4.2"
"@floating-ui/react": "npm:0.26.16"
"@formatjs/intl-durationformat": "npm:^0.2.4"
"@glideapps/glide-data-grid": "npm:^6.0.0"
"@grafana/aws-sdk": "npm:0.3.3"
"@grafana/azure-sdk": "npm:0.0.3"