Pourquoi éviter le formatage manuel ?
Dans beaucoup de projets web, on trouve encore des dates construites à la main avec des split, des montants assemblés avec toFixed(2), ou des libellés comme 12/03/2026 affichés sans tenir compte de la langue de l’utilisateur. Cela semble fonctionner au début, mais ce type de code devient vite fragile.
Un format de date acceptable en France ne l’est pas forcément aux États-Unis. Une virgule décimale peut devenir un point. Le symbole monétaire peut apparaître avant ou après le montant. Certaines langues ont des règles de pluriel différentes. Même l’ordre des informations dans une date peut varier.
L’API Intl, disponible nativement en JavaScript, sert précisément à résoudre ce problème. Elle permet de déléguer le formatage localisé au moteur JavaScript, sans ajouter systématiquement une bibliothèque externe. Pour une application frontend moderne, c’est un outil aussi structurant que les bonnes pratiques de typage des données API en TypeScript ou de progressive enhancement : on améliore la robustesse de l’interface en s’appuyant sur une primitive standard.
Formater une date avec Intl.DateTimeFormat
Le cas le plus courant concerne les dates. Plutôt que d’écrire un format maison, on peut utiliser Intl.DateTimeFormat.
const date = new Date("2026-06-03T14:30:00Z");
const formatter = new Intl.DateTimeFormat("fr-FR", {
dateStyle: "long",
timeStyle: "short",
timeZone: "Europe/Paris"
});
console.log(formatter.format(date));
// Exemple : 3 juin 2026 à 16:30
Plusieurs points sont importants ici. La locale fr-FR indique la langue et la région. Les options dateStyle et timeStyle évitent de définir manuellement chaque morceau du format. Enfin, timeZone rend le résultat explicite, ce qui est essentiel dans une application utilisée par des personnes situées dans plusieurs fuseaux horaires.
On peut aussi construire un format plus précis :
const shortDate = new Intl.DateTimeFormat("fr-FR", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
console.log(shortDate.format(new Date("2026-06-03")));
// 03/06/2026
Cette approche est préférable à une concaténation du type ${day}/${month}/${year}. Elle évite les erreurs de zéro initial, les inversions jour/mois et les ajustements manuels lorsque l’application doit supporter d’autres langues.
Gérer plusieurs locales proprement
Dans une application réelle, la locale ne doit pas être dispersée partout dans le code. Il est préférable de la centraliser, comme on le ferait pour des design tokens en CSS et TypeScript ou pour une configuration API.
export type AppLocale = "fr-FR" | "en-US" | "de-DE";
export const defaultLocale: AppLocale = "fr-FR";
export function createDateFormatter(locale: AppLocale) {
return new Intl.DateTimeFormat(locale, {
dateStyle: "medium",
timeZone: "Europe/Paris"
});
}
Ce type de fonction rend le formatage réutilisable, testable et cohérent. Au lieu d’avoir dix formats légèrement différents dans dix composants, on définit une intention claire : format court, format long, format avec heure, format technique, etc.
Dans React, on peut ensuite injecter cette logique dans un composant simple :
type PublishedAtProps = {
date: string;
locale: "fr-FR" | "en-US";
};
export function PublishedAt({ date, locale }: PublishedAtProps) {
const formatter = new Intl.DateTimeFormat(locale, {
dateStyle: "long"
});
return <time dateTime={date}>{formatter.format(new Date(date))}</time>;
}
L’élément time conserve une valeur machine-readable avec dateTime, tout en affichant une version lisible par l’utilisateur. C’est une bonne pratique à la fois pour l’accessibilité, le SEO et la maintenance HTML.
Formater les nombres avec Intl.NumberFormat
Les nombres posent les mêmes problèmes que les dates. En français, on écrit souvent 1 234,56, tandis qu’en anglais américain on écrira plutôt 1,234.56. Au lieu de manipuler les séparateurs à la main, on peut utiliser Intl.NumberFormat.
const value = 1234.56;
console.log(new Intl.NumberFormat("fr-FR").format(value));
// 1 234,56
console.log(new Intl.NumberFormat("en-US").format(value));
// 1,234.56
Pour les pourcentages, l’API est également adaptée :
const conversionRate = 0.0875;
const percentFormatter = new Intl.NumberFormat("fr-FR", {
style: "percent",
maximumFractionDigits: 1
});
console.log(percentFormatter.format(conversionRate));
// 8,8 %
C’est particulièrement utile dans des tableaux de bord, des interfaces de datavisualisation ou des rapports de performance. Si vous mesurez des métriques avec PerformanceObserver, par exemple, vous pouvez combiner cette approche avec les principes abordés dans PerformanceObserver : mesurer les lenteurs réelles de vos interfaces web.
Formater les devises sans perdre le sens métier
Le formatage monétaire est encore plus sensible. Il ne suffit pas d’ajouter un symbole € après un nombre. Il faut gérer la locale, la devise, les décimales et les conventions d’affichage.
const price = 1499.9;
const priceFormatter = new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR"
});
console.log(priceFormatter.format(price));
// 1 499,90 €
Pour une interface internationale, il faut distinguer la locale de la devise. Un utilisateur francophone peut consulter des prix en dollars, et un utilisateur anglophone peut consulter des prix en euros.
function formatPrice(amount: number, locale: string, currency: string) {
return new Intl.NumberFormat(locale, {
style: "currency",
currency
}).format(amount);
}
console.log(formatPrice(99.99, "fr-FR", "USD"));
console.log(formatPrice(99.99, "en-US", "EUR"));
Cette séparation évite une erreur fréquente : déduire automatiquement la devise à partir de la langue. La langue décrit la présentation ; la devise décrit une donnée métier.
Utiliser Intl.RelativeTimeFormat pour les temps relatifs
Les interfaces modernes affichent souvent des libellés comme “il y a 3 minutes”, “dans 2 jours” ou “le mois dernier”. Là encore, il est tentant d’écrire quelques conditions manuelles. Mais les règles linguistiques changent selon les langues.
const formatter = new Intl.RelativeTimeFormat("fr-FR", {
numeric: "auto"
});
console.log(formatter.format(-1, "day"));
// hier
console.log(formatter.format(3, "day"));
// dans 3 jours
Avec numeric: "auto", le moteur peut produire des mots naturels comme “hier” ou “demain” au lieu de formulations plus mécaniques comme “il y a 1 jour”. C’est un détail, mais ce genre de détail améliore fortement la qualité perçue d’une interface.
On peut encapsuler une logique simple pour choisir l’unité :
function formatRelativeMinutes(minutes: number, locale = "fr-FR") {
const formatter = new Intl.RelativeTimeFormat(locale, {
numeric: "auto"
});
if (Math.abs(minutes) < 60) {
return formatter.format(minutes, "minute");
}
const hours = Math.round(minutes / 60);
return formatter.format(hours, "hour");
}
console.log(formatRelativeMinutes(-12));
console.log(formatRelativeMinutes(-130));
Pour une logique plus complète, il faudra gérer les jours, semaines, mois et années. Mais même dans ce cas, Intl.RelativeTimeFormat reste responsable du rendu linguistique final.
Attention aux performances dans les listes
Créer un formatter Intl n’est pas aussi anodin qu’une simple concaténation. Dans la majorité des interfaces, cela ne posera aucun problème. En revanche, dans une grande liste rendue fréquemment, mieux vaut éviter de recréer le formatter pour chaque élément.
À éviter :
function PriceList({ prices }: { prices: number[] }) {
return (
<ul>
{prices.map((price) => (
<li key={price}>
{new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR"
}).format(price)}
</li>
))}
</ul>
);
}
Préférez créer le formatter une seule fois :
import { useMemo } from "react";
function PriceList({ prices }: { prices: number[] }) {
const formatter = useMemo(() => {
return new Intl.NumberFormat("fr-FR", {
style: "currency",
currency: "EUR"
});
}, []);
return (
<ul>
{prices.map((price) => (
<li key={price}>{formatter.format(price)}</li>
))}
</ul>
);
}
Cette optimisation reste simple, lisible et locale. Elle s’inscrit dans la même logique que les sujets de performance frontend comme les Web Workers en JavaScript ou la Scheduler API : ne pas bloquer l’interface avec du travail évitable.
Structurer un module de formatage
Dans une base de code TypeScript, je recommande souvent de créer un petit module dédié au formatage. Il évite de répéter les options Intl dans tous les composants.
export function formatDate(date: string | Date, locale = "fr-FR") {
return new Intl.DateTimeFormat(locale, {
dateStyle: "long"
}).format(new Date(date));
}
export function formatCurrency(
amount: number,
currency = "EUR",
locale = "fr-FR"
) {
return new Intl.NumberFormat(locale, {
style: "currency",
currency
}).format(amount);
}
export function formatPercent(value: number, locale = "fr-FR") {
return new Intl.NumberFormat(locale, {
style: "percent",
maximumFractionDigits: 1
}).format(value);
}
Ce module devient une frontière claire entre les données brutes et leur représentation utilisateur. Les composants n’ont plus besoin de connaître les options exactes de formatage. Ils demandent simplement un rendu adapté.
Les erreurs fréquentes à éviter
Première erreur : stocker des valeurs déjà formatées dans l’état applicatif. Un prix doit rester un nombre, une date doit rester une date ou une chaîne ISO, un pourcentage doit rester une valeur numérique. Le formatage doit intervenir au plus près de l’affichage.
Deuxième erreur : confondre validation et présentation. Intl formate des valeurs, mais ne valide pas à lui seul les données reçues. Pour sécuriser une réponse API, il faut d’abord vérifier la structure des données, par exemple avec les techniques présentées dans Typage des données API en TypeScript, puis formater les valeurs validées.
Troisième erreur : oublier l’accessibilité. Une date relative comme “hier” peut être agréable, mais il est souvent utile de conserver une date exacte dans un attribut dateTime ou un texte complémentaire. L’objectif n’est pas seulement de faire joli, mais de rendre l’information compréhensible.
Conclusion
L’API Intl est une brique discrète mais essentielle du développement web moderne. Elle permet de remplacer des formats bricolés par des formats localisés, maintenables et cohérents. Dates, heures, nombres, devises, pourcentages et temps relatifs peuvent être gérés avec des primitives natives, sans réinventer des règles linguistiques complexes.
Bien utilisée, elle améliore la qualité de l’expérience utilisateur tout en simplifiant le code. Le bon réflexe consiste à conserver des données brutes propres dans l’application, puis à centraliser leur présentation dans des fonctions ou composants dédiés. C’est une petite décision d’architecture, mais elle évite beaucoup d’incohérences dans les interfaces qui grandissent.