Skip to content

Commit 309c454

Browse files
committed
feat: support locale prop
1 parent 946eced commit 309c454

File tree

9 files changed

+467
-8
lines changed

9 files changed

+467
-8
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"test": "vitest",
1414
"package": "bun --run dlz",
1515
"format": "bun --bun prettier --write . --cache",
16-
"upgrade-examples": "bun scripts/upgrade-examples.ts"
16+
"upgrade-examples": "bun scripts/upgrade-examples.ts",
17+
"generate-locales": "bun scripts/generate-locales.ts"
1718
},
1819
"dependencies": {
1920
"dayjs": "^1.11.13"

scripts/generate-locales.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import localeDayjs from "dayjs/locale.json" with { type: "json" };
2+
import { format } from "prettier";
3+
4+
const locales: string[] = [];
5+
6+
for (const { key, name } of localeDayjs) {
7+
if (key === "en") {
8+
locales.unshift(`'${key}'`);
9+
} else {
10+
locales.push(`'${key}'`);
11+
}
12+
}
13+
14+
const localesType = await format(
15+
`/** @default "en" */\nexport type Locales = ${locales.join(" | ")};`,
16+
{ parser: "typescript" },
17+
);
18+
Bun.write("src/locales.d.ts", localesType);
19+
20+
export {};

src/Time.svelte

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
* @type {boolean | number}
2828
*/
2929
live = false,
30+
31+
/**
32+
* The locale to use for formatting
33+
* @type {import("./locales").Locales}
34+
*/
35+
locale = "en",
3036
...rest
3137
} = $props();
3238
@@ -40,7 +46,7 @@
4046
if (relative && live !== false) {
4147
interval = setInterval(
4248
() => {
43-
formatted = dayjs(timestamp).from();
49+
formatted = dayjs(timestamp).locale(locale).from(dayjs());
4450
},
4551
Math.abs(typeof live === "number" ? live : DEFAULT_INTERVAL),
4652
);
@@ -54,11 +60,13 @@
5460
* @type {string}
5561
*/
5662
let formatted = $state(
57-
relative ? dayjs(timestamp).from() : dayjs(timestamp).format(format),
63+
relative
64+
? dayjs(timestamp).locale(locale).from(dayjs())
65+
: dayjs(timestamp).locale(locale).format(format),
5866
);
5967
6068
const title = $derived(
61-
relative ? dayjs(timestamp).format(format) : undefined,
69+
relative ? dayjs(timestamp).locale(locale).format(format) : undefined,
6270
);
6371
</script>
6472

src/Time.svelte.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ConfigType, OptionType } from "dayjs";
22
import type { Component } from "svelte";
33
import type { SvelteHTMLElements } from "svelte/elements";
4+
import type { Locales } from "./locales";
45

56
type RestProps = SvelteHTMLElements["time"];
67

@@ -33,6 +34,12 @@ export interface TimeProps extends RestProps {
3334
*/
3435
live?: boolean | number;
3536

37+
/**
38+
* The locale to use for formatting
39+
* @default "en"
40+
*/
41+
locale?: Locales;
42+
3643
/**
3744
* Formatted timestamp.
3845
* Result of invoking `dayjs().format()`

src/locales.d.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/** @default "en" */
2+
export type Locales =
3+
| "en"
4+
| "af"
5+
| "am"
6+
| "ar-dz"
7+
| "ar-iq"
8+
| "ar-kw"
9+
| "ar-ly"
10+
| "ar-ma"
11+
| "ar-sa"
12+
| "ar-tn"
13+
| "ar"
14+
| "az"
15+
| "be"
16+
| "bg"
17+
| "bi"
18+
| "bm"
19+
| "bn-bd"
20+
| "bn"
21+
| "bo"
22+
| "br"
23+
| "bs"
24+
| "ca"
25+
| "cs"
26+
| "cv"
27+
| "cy"
28+
| "de-at"
29+
| "da"
30+
| "de-ch"
31+
| "de"
32+
| "dv"
33+
| "el"
34+
| "en-au"
35+
| "en-ca"
36+
| "en-gb"
37+
| "en-ie"
38+
| "en-il"
39+
| "en-in"
40+
| "en-nz"
41+
| "en-sg"
42+
| "en-tt"
43+
| "eo"
44+
| "es-do"
45+
| "es-mx"
46+
| "es-pr"
47+
| "es-us"
48+
| "et"
49+
| "es"
50+
| "eu"
51+
| "fa"
52+
| "fo"
53+
| "fi"
54+
| "fr-ca"
55+
| "fr-ch"
56+
| "fr"
57+
| "fy"
58+
| "ga"
59+
| "gd"
60+
| "gom-latn"
61+
| "gl"
62+
| "gu"
63+
| "he"
64+
| "hi"
65+
| "hr"
66+
| "hu"
67+
| "ht"
68+
| "hy-am"
69+
| "id"
70+
| "is"
71+
| "it-ch"
72+
| "it"
73+
| "ja"
74+
| "jv"
75+
| "ka"
76+
| "kk"
77+
| "km"
78+
| "kn"
79+
| "ko"
80+
| "ku"
81+
| "ky"
82+
| "lb"
83+
| "lo"
84+
| "lt"
85+
| "lv"
86+
| "me"
87+
| "mi"
88+
| "mk"
89+
| "ml"
90+
| "mn"
91+
| "mr"
92+
| "ms-my"
93+
| "ms"
94+
| "mt"
95+
| "my"
96+
| "nb"
97+
| "ne"
98+
| "nl-be"
99+
| "nl"
100+
| "pl"
101+
| "pt-br"
102+
| "pt"
103+
| "rn"
104+
| "ro"
105+
| "ru"
106+
| "rw"
107+
| "sd"
108+
| "se"
109+
| "si"
110+
| "sk"
111+
| "sl"
112+
| "sq"
113+
| "sr-cyrl"
114+
| "ss"
115+
| "sv-fi"
116+
| "sr"
117+
| "sv"
118+
| "sw"
119+
| "ta"
120+
| "te"
121+
| "tet"
122+
| "tg"
123+
| "th"
124+
| "tk"
125+
| "tl-ph"
126+
| "tlh"
127+
| "tr"
128+
| "tzl"
129+
| "tzm-latn"
130+
| "tzm"
131+
| "ug-cn"
132+
| "uk"
133+
| "ur"
134+
| "uz-latn"
135+
| "uz"
136+
| "vi"
137+
| "x-pseudo"
138+
| "yo"
139+
| "zh-cn"
140+
| "zh-hk"
141+
| "zh-tw"
142+
| "zh"
143+
| "oc-lnc"
144+
| "nn"
145+
| "pa-in";

src/svelte-time.svelte.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { TimeProps } from "./Time.svelte";
44
export interface SvelteTimeOptions
55
extends Pick<
66
TimeProps,
7-
"timestamp" | "format" | "relative" | "live" | "title"
7+
"timestamp" | "format" | "relative" | "live" | "title" | "locale"
88
> {}
99

1010
export const svelteTime: Action<

src/svelte-time.svelte.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export const svelteTime = (node, options = {}) => {
1717
const format = options.format || "MMM DD, YYYY";
1818
const relative = options.relative === true;
1919
const live = options.live ?? false;
20+
const locale = options.locale ?? "en";
2021

21-
let formatted_from = dayjs(timestamp).from();
22-
let formatted = dayjs(timestamp).format(format);
22+
let formatted_from = dayjs(timestamp).locale(locale).from();
23+
let formatted = dayjs(timestamp).locale(locale).format(format);
2324

2425
if (relative) {
2526
if ("title" in options) {
@@ -33,7 +34,7 @@ export const svelteTime = (node, options = {}) => {
3334
if (live !== false) {
3435
interval = setInterval(
3536
() => {
36-
node.innerText = dayjs(timestamp).from();
37+
node.innerText = dayjs(timestamp).locale(locale).from();
3738
},
3839
Math.abs(typeof live === "number" ? live : DEFAULT_INTERVAL),
3940
);

tests/SvelteTimeLocale.test.svelte

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<script lang="ts">
2+
import Time, { svelteTime } from "svelte-time";
3+
import dayjs from "dayjs";
4+
import "dayjs/locale/de"; // German
5+
import "dayjs/locale/es"; // Spanish
6+
import "dayjs/locale/fr"; // French
7+
import "dayjs/locale/ja"; // Japanese
8+
9+
const fixedDate = "2024-01-01T12:00:00.000Z";
10+
11+
// Pre-format dates with locales
12+
const germanDate = dayjs(fixedDate).locale("de").format("dddd, D. MMMM YYYY");
13+
const spanishDate = dayjs(fixedDate)
14+
.locale("es")
15+
.format("dddd, D [de] MMMM [de] YYYY");
16+
const frenchDate = dayjs(fixedDate).locale("fr").format("dddd D MMMM YYYY");
17+
const japaneseDate = dayjs(fixedDate)
18+
.locale("ja")
19+
.format("YYYY年M月D日(dddd)");
20+
</script>
21+
22+
<!-- German Locale -->
23+
<Time
24+
data-test="german-full"
25+
timestamp={fixedDate}
26+
format="dddd, D. MMMM YYYY"
27+
locale="de"
28+
/>
29+
30+
<span data-test="german-formatted">{germanDate}</span>
31+
32+
<Time
33+
data-test="german-short"
34+
timestamp={fixedDate}
35+
format="dd., D. MMM YYYY"
36+
locale="de"
37+
/>
38+
39+
<Time
40+
data-test="german-month-year"
41+
timestamp={fixedDate}
42+
format="MMMM YYYY"
43+
locale="de"
44+
/>
45+
46+
<!-- Spanish Locale -->
47+
<Time
48+
data-test="spanish-full"
49+
timestamp={fixedDate}
50+
format="dddd, D [de] MMMM [de] YYYY"
51+
locale="es"
52+
/>
53+
54+
<span data-test="spanish-formatted">{spanishDate}</span>
55+
56+
<Time
57+
data-test="spanish-short"
58+
timestamp={fixedDate}
59+
format="dd., D MMM YYYY"
60+
locale="es"
61+
/>
62+
63+
<!-- French Locale -->
64+
<Time
65+
data-test="french-full"
66+
timestamp={fixedDate}
67+
format="dddd D MMMM YYYY"
68+
locale="fr"
69+
/>
70+
71+
<span data-test="french-formatted">{frenchDate}</span>
72+
73+
<Time
74+
data-test="french-short"
75+
timestamp={fixedDate}
76+
format="dd. D MMM YYYY"
77+
locale="fr"
78+
/>
79+
80+
<!-- Japanese Locale -->
81+
<Time
82+
data-test="japanese-full"
83+
timestamp={fixedDate}
84+
format="YYYY年M月D日(dddd)"
85+
locale="ja"
86+
/>
87+
88+
<span data-test="japanese-formatted">{japaneseDate}</span>
89+
90+
<Time
91+
data-test="japanese-short"
92+
timestamp={fixedDate}
93+
format="YYYY年M月D日"
94+
locale="ja"
95+
/>
96+
97+
<!-- Relative Time in Different Locales -->
98+
<Time data-test="german-relative" timestamp={fixedDate} relative locale="de" />
99+
100+
<Time data-test="spanish-relative" timestamp={fixedDate} relative locale="es" />
101+
102+
<Time data-test="french-relative" timestamp={fixedDate} relative locale="fr" />
103+
104+
<Time
105+
data-test="japanese-relative"
106+
timestamp={fixedDate}
107+
relative
108+
locale="ja"
109+
/>
110+
111+
<time data-test="action-locale-es" use:svelteTime={{ locale: "es" }}></time>
112+
113+
<time data-test="action-locale-fr" use:svelteTime={{ locale: "fr" }}></time>
114+
115+
<time data-test="action-locale-ja" use:svelteTime={{ locale: "ja" }}></time>
116+
117+
<time data-test="action-locale-de" use:svelteTime={{ locale: "de" }}></time>

0 commit comments

Comments
 (0)