import type { NextPage } from "next"; import Head from "next/head"; import { ChangeEvent, useEffect } from "react"; import { useMemo } from "react"; import { useState } from "react"; import { ComponentType } from "react"; import { LangContext } from "../dist/cjs"; import { canonicaliseLanguage, parseAcceptLanguage, canonicaliseLocales, LocaleStack, searchOrder, useTranslation, TString, T, Translate } from "../dist/esm"; // import { searchOrder } from "../dist/esm/searchOrder"; import { Links } from "../lib/links"; import styles from "../styles/Calculator.module.css"; const canonicalise = (lang: string) => { const c = canonicaliseLanguage(lang); return c ? [c] : []; }; const parseLangs = (inp: string) => inp.split(/\s+/).flatMap(canonicalise); function parseInput(inp: string): { stack: LocaleStack; type: string } { return /^\s*$/.test(inp) ? { stack: [], type: "is-empty" } : /[;,]/.test(inp) ? { stack: parseAcceptLanguage(inp), type: "is-accept" } : { stack: canonicaliseLocales(parseLangs(inp)), type: "is-stack" }; } const stackCache = new WeakMap(); const randInt = (min: number, max: number): number => Math.floor(min + Math.random() * (max - min)); const randomColour = () => `hsl(${randInt(0, 360)}deg, ${randInt(80, 100)}%, ${randInt(65, 80)}%)`; const stackColour = (stack: LocaleStack): string => { let colour = stackCache.get(stack); if (!colour) stackCache.set(stack, (colour = randomColour())); return colour; }; // Which APIs? const API = [ "Collator", "DateTimeFormat", "ListFormat", "NumberFormat", "PluralRules", "RelativeTimeFormat", "Segmenter" ]; interface Judgement { state: "fail" | "exact" | "miss" | "match"; reason: string; locale?: string; } const bail = (reason: string): Judgement => ({ state: "fail", reason }); const js = (x: any) => JSON.stringify(x, null, 2); const tryApi = (langs: LocaleStack, api: string) => { const impl = (Intl as { [key: string]: any })[api]; if (!impl) return bail(`Intl.${api} is not implemented`); const intl = new impl(langs); if (typeof intl.resolvedOptions !== "function") return bail(`Intl.${api} does not implement resolvedOptions()`); const opt = intl.resolvedOptions(); if (!opt || typeof opt !== "object") return bail(`Intl.${api}.resolvedOptions() doesn't return an object`); const { locale } = opt; if (!locale) return bail(`Intl.${api}.resolvedOptions() doesn't return a locale`); if (locale === langs[0]) return { state: "exact", reason: `matched as ${js(locale)}` }; const state = langs.includes(locale) ? "match" : "miss"; return { state, locale, reason: `resolved as ${js(locale)}` }; }; const APIStatus: ComponentType<{ search: LocaleStack }> = ({ search }) => { const verdict = useMemo( () => API.map(api => ({ api, status: tryApi(search, api) })), [search] ); return (

API Support

Here's how the Intl APIs resolve {js(search)}

{verdict.map(({ api, status }) => ( ))}
Intl.{api} {status.state} ({status.reason})
); }; const DateFormat: ComponentType<{ date: Date }> = ({ date }) => { const ctx = useTranslation(); const { dtf, locale } = useMemo(() => { const dtf = new Intl.DateTimeFormat(ctx.search, { dateStyle: "full", timeStyle: "full" }); const { locale } = dtf.resolvedOptions(); return { dtf, locale }; }, [ctx.search]); const ts = TString.literal(dtf.format(date), locale); return ; }; const Clock: ComponentType = () => { const [now, setNow] = useState(null); useEffect(() => { const timer = setInterval(() => setNow(new Date()), 500); return () => clearInterval(timer); }, []); return ; }; const Stack: ComponentType<{ stack: LocaleStack; title: string }> = ({ stack, title }) => { const color = stackColour(stack); return (

{title}

    {stack.length ? stack.map((lang, i) =>
  • {lang}
  • ) : null}
); }; const tryArray = (str: string): string | undefined => { try { const obj = JSON.parse(str); if (Array.isArray(obj) && obj.every(s => typeof s === "string")) return obj.join(" "); } catch (e) {} }; const tryJSON = (str: string) => tryArray(str) || tryArray(`[${str}]`); const maybeJSON = (str: string) => tryJSON(str.replace(/,\s*$/, "")) || str; const Calculator: ComponentType<{ init?: string; children?: Function }> = ({ init = "", children }) => { const [inp, setInp] = useState(init); const inpChange = (e: ChangeEvent) => { setInp(maybeJSON(e.target.value)); }; const { type, stack } = useMemo(() => parseInput(inp), [inp]); const search = useMemo(() => searchOrder(stack), [stack]); return (
{children && children(search)}
); }; const CalculatorPage: NextPage = () => { return (
Language Search Calculator

Language Stack Calculator

{(search: LocaleStack) => }
); }; export default CalculatorPage;