diff --git a/apps/v4/app/components/docs/examples/calendar-locale.gts b/apps/v4/app/components/docs/examples/calendar-locale.gts
new file mode 100644
index 0000000..97ffb26
--- /dev/null
+++ b/apps/v4/app/components/docs/examples/calendar-locale.gts
@@ -0,0 +1,27 @@
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { nl } from 'date-fns/locale';
+
+import { Calendar } from '@/components/ui/calendar';
+
+import type { DateRange } from '@/components/ui/calendar';
+
+export default class CalendarLocale extends Component {
+ locale = nl;
+
+ @tracked date: Date | undefined = new Date();
+
+ handleSelect = (date: Date | DateRange | undefined) => {
+ this.date = date as Date | undefined;
+ };
+
+
+
+
+}
diff --git a/apps/v4/app/components/docs/examples/index.ts b/apps/v4/app/components/docs/examples/index.ts
index d4f609d..095b5b6 100644
--- a/apps/v4/app/components/docs/examples/index.ts
+++ b/apps/v4/app/components/docs/examples/index.ts
@@ -39,6 +39,7 @@ export { default as CalendarBookedDates } from './calendar-booked-dates';
export { default as CalendarCaption } from './calendar-caption';
export { default as CalendarCustomDays } from './calendar-custom-days';
export { default as CalendarDemo } from './calendar-demo';
+export { default as CalendarLocale } from './calendar-locale';
export { default as CalendarPresets } from './calendar-presets';
export { default as CalendarRange } from './calendar-range';
export { default as CalendarTime } from './calendar-time';
diff --git a/apps/v4/app/content/docs/components/calendar.md b/apps/v4/app/content/docs/components/calendar.md
index 8cfbd26..f184d19 100644
--- a/apps/v4/app/content/docs/components/calendar.md
+++ b/apps/v4/app/content/docs/components/calendar.md
@@ -122,6 +122,26 @@ Use `@showWeekNumber` to show week numbers.
+### Locale
+
+Use `@locale` to localize the calendar. Pass a [date-fns locale](https://date-fns.org/docs/Locale) to translate month names, weekday labels, and set the first day of the week. Use `@weekStartsOn` to override the week start day.
+
+```gts showLineNumbers
+import { nl } from 'date-fns/locale';
+```
+
+```hbs showLineNumbers
+
+```
+
+
+
## API Reference
| Argument | Type | Default | Description |
@@ -141,6 +161,8 @@ Use `@showWeekNumber` to show week numbers.
| `@showWeekNumber` | `boolean` | `false` | Show week numbers. |
| `@fixedWeeks` | `boolean` | `false` | Always show 6 weeks. |
| `@buttonVariant` | `'default' \| 'ghost' \| 'outline'` | `'ghost'` | Variant for nav buttons. |
+| `@locale` | `Locale` | — | date-fns locale for i18n. |
+| `@weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | First day of the week (0=Sun). |
| `@startMonth` | `Date` | — | Earliest month navigable. |
| `@endMonth` | `Date` | — | Latest month navigable. |
| `@class` | `string` | — | Additional CSS classes. |
diff --git a/apps/v4/registry/new-york-v4/ui/calendar.gts b/apps/v4/registry/new-york-v4/ui/calendar.gts
index 3bc5d6a..bff7464 100644
--- a/apps/v4/registry/new-york-v4/ui/calendar.gts
+++ b/apps/v4/registry/new-york-v4/ui/calendar.gts
@@ -22,6 +22,7 @@ import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import type Owner from '@ember/owner';
+import type { Locale } from 'date-fns/locale';
import ChevronDown from '~icons/lucide/chevron-down';
import ChevronLeft from '~icons/lucide/chevron-left';
@@ -51,6 +52,8 @@ interface CalendarSignature {
fixedWeeks?: boolean;
class?: string;
buttonVariant?: 'default' | 'ghost' | 'outline';
+ locale?: Locale;
+ weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
timeZone?: string;
startMonth?: Date;
endMonth?: Date;
@@ -88,8 +91,8 @@ interface MonthData {
weeks: WeekInfo[];
}
-const WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
-const MONTH_NAMES_SHORT = [
+const DEFAULT_WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
+const DEFAULT_MONTH_NAMES_SHORT = [
'Jan',
'Feb',
'Mar',
@@ -122,8 +125,8 @@ function selectedSingle(day: DayInfo): boolean {
);
}
-function monthNameShort(index: number): string {
- return MONTH_NAMES_SHORT[index] ?? '';
+function monthNameShort(index: number, monthNames: string[]): string {
+ return monthNames[index] ?? '';
}
class Calendar extends Component {
@@ -153,6 +156,42 @@ class Calendar extends Component {
return this.args.buttonVariant ?? 'ghost';
}
+ get weekStartsOn(): 0 | 1 | 2 | 3 | 4 | 5 | 6 {
+ return (
+ this.args.weekStartsOn ?? this.args.locale?.options?.weekStartsOn ?? 0
+ );
+ }
+
+ get weekOptions(): { weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 } {
+ return { weekStartsOn: this.weekStartsOn };
+ }
+
+ get weekdayLabels(): string[] {
+ if (this.args.locale) {
+ const refDate = startOfWeek(new Date(), this.weekOptions);
+ return Array.from({ length: 7 }, (_, i) =>
+ format(addDays(refDate, i), 'EEEEEE', {
+ locale: this.args.locale,
+ })
+ );
+ }
+ const wso = this.weekStartsOn;
+ if (wso === 0) return DEFAULT_WEEKDAY_LABELS;
+ return [
+ ...DEFAULT_WEEKDAY_LABELS.slice(wso),
+ ...DEFAULT_WEEKDAY_LABELS.slice(0, wso),
+ ];
+ }
+
+ get monthNamesShort(): string[] {
+ if (this.args.locale) {
+ return Array.from({ length: 12 }, (_, i) =>
+ format(new Date(2024, i, 1), 'MMM', { locale: this.args.locale })
+ );
+ }
+ return DEFAULT_MONTH_NAMES_SHORT;
+ }
+
get displayMonth(): Date {
if (this.args.month) {
return startOfMonth(this.args.month);
@@ -190,7 +229,7 @@ class Calendar extends Component {
}
get monthOptions(): { value: number; label: string }[] {
- return MONTH_NAMES_SHORT.map((label, i) => ({ value: i, label }));
+ return this.monthNamesShort.map((label, i) => ({ value: i, label }));
}
get months(): MonthData[] {
@@ -217,8 +256,8 @@ class Calendar extends Component {
buildMonthData(monthDate: Date): MonthData {
const monthStart = startOfMonth(monthDate);
const monthEnd = endOfMonth(monthDate);
- const calendarStart = startOfWeek(monthStart);
- const calendarEnd = endOfWeek(monthEnd);
+ const calendarStart = startOfWeek(monthStart, this.weekOptions);
+ const calendarEnd = endOfWeek(monthEnd, this.weekOptions);
const allDays = eachDayOfInterval({
start: calendarStart,
@@ -262,7 +301,7 @@ class Calendar extends Component {
});
weeks.push({
- weekNumber: getWeek(weekDays[0]!),
+ weekNumber: getWeek(weekDays[0]!, this.weekOptions),
days,
});
}
@@ -270,8 +309,10 @@ class Calendar extends Component {
return {
month: monthDate.getMonth(),
year: monthDate.getFullYear(),
- label: format(monthDate, 'MMMM yyyy'),
- labelShort: format(monthDate, 'MMM'),
+ label: format(monthDate, 'MMMM yyyy', {
+ locale: this.args.locale,
+ }),
+ labelShort: format(monthDate, 'MMM', { locale: this.args.locale }),
weeks,
};
}
@@ -471,7 +512,7 @@ class Calendar extends Component {
- {{monthNameShort monthData.month}}
+ {{monthNameShort monthData.month this.monthNamesShort}}