diff --git a/package.json b/package.json index 72b4086..665f47c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test": "vitest", "package": "bun --run dlz", "format": "bun --bun prettier --write . --cache", - "upgrade-examples": "bun scripts/upgrade-examples.ts" + "upgrade-examples": "bun scripts/upgrade-examples.ts", + "generate-locales": "bun scripts/generate-locales.ts" }, "dependencies": { "dayjs": "^1.11.13" diff --git a/scripts/generate-locales.ts b/scripts/generate-locales.ts new file mode 100644 index 0000000..89c71ae --- /dev/null +++ b/scripts/generate-locales.ts @@ -0,0 +1,20 @@ +import localeDayjs from "dayjs/locale.json" with { type: "json" }; +import { format } from "prettier"; + +const locales: string[] = []; + +for (const { key, name } of localeDayjs) { + if (key === "en") { + locales.unshift(`'${key}'`); + } else { + locales.push(`'${key}'`); + } +} + +const localesType = await format( + `/** @default "en" */\nexport type Locales = ${locales.join(" | ")};`, + { parser: "typescript" }, +); +Bun.write("src/locales.d.ts", localesType); + +export {}; diff --git a/src/Time.svelte b/src/Time.svelte index a0a2bd8..ce92827 100644 --- a/src/Time.svelte +++ b/src/Time.svelte @@ -27,6 +27,12 @@ * @type {boolean | number} */ live = false, + + /** + * The locale to use for formatting + * @type {import("./locales").Locales} + */ + locale = "en", ...rest } = $props(); @@ -40,7 +46,7 @@ if (relative && live !== false) { interval = setInterval( () => { - formatted = dayjs(timestamp).from(); + formatted = dayjs(timestamp).locale(locale).from(dayjs()); }, Math.abs(typeof live === "number" ? live : DEFAULT_INTERVAL), ); @@ -54,11 +60,13 @@ * @type {string} */ let formatted = $state( - relative ? dayjs(timestamp).from() : dayjs(timestamp).format(format), + relative + ? dayjs(timestamp).locale(locale).from(dayjs()) + : dayjs(timestamp).locale(locale).format(format), ); const title = $derived( - relative ? dayjs(timestamp).format(format) : undefined, + relative ? dayjs(timestamp).locale(locale).format(format) : undefined, ); diff --git a/src/Time.svelte.d.ts b/src/Time.svelte.d.ts index 1a419d7..49ddfc2 100644 --- a/src/Time.svelte.d.ts +++ b/src/Time.svelte.d.ts @@ -1,6 +1,7 @@ import type { ConfigType, OptionType } from "dayjs"; import type { Component } from "svelte"; import type { SvelteHTMLElements } from "svelte/elements"; +import type { Locales } from "./locales"; type RestProps = SvelteHTMLElements["time"]; @@ -33,6 +34,12 @@ export interface TimeProps extends RestProps { */ live?: boolean | number; + /** + * The locale to use for formatting + * @default "en" + */ + locale?: Locales; + /** * Formatted timestamp. * Result of invoking `dayjs().format()` diff --git a/src/locales.d.ts b/src/locales.d.ts new file mode 100644 index 0000000..9db3069 --- /dev/null +++ b/src/locales.d.ts @@ -0,0 +1,145 @@ +/** @default "en" */ +export type Locales = + | "en" + | "af" + | "am" + | "ar-dz" + | "ar-iq" + | "ar-kw" + | "ar-ly" + | "ar-ma" + | "ar-sa" + | "ar-tn" + | "ar" + | "az" + | "be" + | "bg" + | "bi" + | "bm" + | "bn-bd" + | "bn" + | "bo" + | "br" + | "bs" + | "ca" + | "cs" + | "cv" + | "cy" + | "de-at" + | "da" + | "de-ch" + | "de" + | "dv" + | "el" + | "en-au" + | "en-ca" + | "en-gb" + | "en-ie" + | "en-il" + | "en-in" + | "en-nz" + | "en-sg" + | "en-tt" + | "eo" + | "es-do" + | "es-mx" + | "es-pr" + | "es-us" + | "et" + | "es" + | "eu" + | "fa" + | "fo" + | "fi" + | "fr-ca" + | "fr-ch" + | "fr" + | "fy" + | "ga" + | "gd" + | "gom-latn" + | "gl" + | "gu" + | "he" + | "hi" + | "hr" + | "hu" + | "ht" + | "hy-am" + | "id" + | "is" + | "it-ch" + | "it" + | "ja" + | "jv" + | "ka" + | "kk" + | "km" + | "kn" + | "ko" + | "ku" + | "ky" + | "lb" + | "lo" + | "lt" + | "lv" + | "me" + | "mi" + | "mk" + | "ml" + | "mn" + | "mr" + | "ms-my" + | "ms" + | "mt" + | "my" + | "nb" + | "ne" + | "nl-be" + | "nl" + | "pl" + | "pt-br" + | "pt" + | "rn" + | "ro" + | "ru" + | "rw" + | "sd" + | "se" + | "si" + | "sk" + | "sl" + | "sq" + | "sr-cyrl" + | "ss" + | "sv-fi" + | "sr" + | "sv" + | "sw" + | "ta" + | "te" + | "tet" + | "tg" + | "th" + | "tk" + | "tl-ph" + | "tlh" + | "tr" + | "tzl" + | "tzm-latn" + | "tzm" + | "ug-cn" + | "uk" + | "ur" + | "uz-latn" + | "uz" + | "vi" + | "x-pseudo" + | "yo" + | "zh-cn" + | "zh-hk" + | "zh-tw" + | "zh" + | "oc-lnc" + | "nn" + | "pa-in"; diff --git a/src/svelte-time.svelte.d.ts b/src/svelte-time.svelte.d.ts index 52ccc1d..138ace6 100644 --- a/src/svelte-time.svelte.d.ts +++ b/src/svelte-time.svelte.d.ts @@ -4,7 +4,7 @@ import type { TimeProps } from "./Time.svelte"; export interface SvelteTimeOptions extends Pick< TimeProps, - "timestamp" | "format" | "relative" | "live" | "title" + "timestamp" | "format" | "relative" | "live" | "title" | "locale" > {} export const svelteTime: Action< diff --git a/src/svelte-time.svelte.js b/src/svelte-time.svelte.js index c1ca845..d8f0b6b 100644 --- a/src/svelte-time.svelte.js +++ b/src/svelte-time.svelte.js @@ -17,9 +17,10 @@ export const svelteTime = (node, options = {}) => { const format = options.format || "MMM DD, YYYY"; const relative = options.relative === true; const live = options.live ?? false; + const locale = options.locale ?? "en"; - let formatted_from = dayjs(timestamp).from(); - let formatted = dayjs(timestamp).format(format); + let formatted_from = dayjs(timestamp).locale(locale).from(); + let formatted = dayjs(timestamp).locale(locale).format(format); if (relative) { if ("title" in options) { @@ -33,7 +34,7 @@ export const svelteTime = (node, options = {}) => { if (live !== false) { interval = setInterval( () => { - node.innerText = dayjs(timestamp).from(); + node.innerText = dayjs(timestamp).locale(locale).from(); }, Math.abs(typeof live === "number" ? live : DEFAULT_INTERVAL), ); diff --git a/tests/SvelteTimeLocale.test.svelte b/tests/SvelteTimeLocale.test.svelte new file mode 100644 index 0000000..492f1ba --- /dev/null +++ b/tests/SvelteTimeLocale.test.svelte @@ -0,0 +1,117 @@ + + + +