feat: add locale support

master
Guillermo Pages 4 months ago
parent 50ea538717
commit cca9b10a45

@ -6,12 +6,14 @@ import { DateRangeView } from './examples/DateRangeView';
import { InteractiveDateRange } from './examples/InteractiveDateRange';
import { CompactYear } from './examples/CompactYear';
import { ComprehensiveColorScheme } from './examples/ComprehensiveColorScheme';
import { LocaleSupport } from './examples/LocaleSupport';
import styles from './App.module.scss';
const App: React.FC = () => {
return (
<div className={styles.container}>
<DateRangePicker />
<LocaleSupport />
<ComprehensiveColorScheme />
<SingleMonth />
<WeekView />

@ -18,7 +18,8 @@ export const Day: React.FC<DayProps> = ({
fontProportion = 100,
magnify = false,
activeColors,
colorScheme
colorScheme,
locale
}) => {
const isInteractive = Boolean(onSelect || onHover);
@ -102,7 +103,7 @@ export const Day: React.FC<DayProps> = ({
{headerStyle !== 'none' && (
<div className={headerClasses} style={headerCustomStyles}>
<div className={styles.Day__HeaderText}>
{getDayLabel(date, headerStyle)}
{getDayLabel(date, headerStyle, locale)}
</div>
</div>
)}

@ -1,5 +1,6 @@
import React from 'react';
import { startOfMonth, startOfWeek, addDays, addWeeks, endOfMonth, format } from 'date-fns';
import { enUS } from 'date-fns/locale';
import { MonthProps, HeaderStyle } from '../../../types/calendar';
import { Week } from '../Week';
import styles from './Month.module.scss';
@ -20,7 +21,8 @@ export const Month: React.FC<MonthProps> = ({
magnify = false,
activeColors,
activeDates,
colorScheme
colorScheme,
locale = enUS
}) => {
const monthStart = startOfMonth(date);
const start = startOfWeek(monthStart, { weekStartsOn: 1 });
@ -51,6 +53,7 @@ export const Month: React.FC<MonthProps> = ({
activeColors={activeColors}
activeDates={activeDates}
colorScheme={colorScheme}
locale={locale}
/>
);
current = addWeeks(current, 1);
@ -58,7 +61,7 @@ export const Month: React.FC<MonthProps> = ({
return (
<div className={styles.Month}>
<h2 className={styles.Month__Title}>{format(date, 'MMMM yyyy')}</h2>
<h2 className={styles.Month__Title}>{format(date, 'MMMM yyyy', { locale })}</h2>
<div className={classNames(styles.Month__Container, {
[styles['Month__Container--row']]: direction === 'row',
[styles['Month__Container--column']]: direction === 'column'
@ -77,7 +80,7 @@ export const Month: React.FC<MonthProps> = ({
[styles['Month__DayHeader--weekend']]: isWeekend
})}
>
{format(dayDate, 'EEEEE')}
{format(dayDate, 'EEEEE', { locale })}
</div>
);
})}

@ -20,7 +20,8 @@ export const Week: React.FC<WeekProps> = ({
magnify = false,
activeColors,
activeDates = [],
colorScheme
colorScheme,
locale
}) => {
const allDays = Array.from({ length: 7 }, (_, i) => {
const date = addDays(startDate, i);
@ -63,6 +64,7 @@ export const Week: React.FC<WeekProps> = ({
magnify={magnify}
activeColors={activeColors}
colorScheme={colorScheme}
locale={locale}
/>
</div>
);
@ -81,6 +83,7 @@ export const Week: React.FC<WeekProps> = ({
magnify={magnify}
activeColors={activeColors}
colorScheme={colorScheme}
locale={locale}
/>
</div>
);

@ -17,7 +17,8 @@ export const Year: React.FC<YearProps> = ({
magnify = false,
activeColors,
activeDates,
colorScheme
colorScheme,
locale
}) => {
const months = Array.from({ length: 12 }, (_, index) => {
const monthDate = new Date(year, index, 1);
@ -39,6 +40,7 @@ export const Year: React.FC<YearProps> = ({
activeColors={activeColors}
activeDates={activeDates}
colorScheme={colorScheme}
locale={locale}
/>
);
});

@ -1,5 +1,6 @@
import { HeaderStyle } from '../../types/calendar';
import { format } from 'date-fns';
import { format, Locale } from 'date-fns';
import { enUS } from 'date-fns/locale';
export const getHeightByHeaderStyle = (headerStyle: HeaderStyle) => {
const baseHeights: Record<HeaderStyle, [string, string]> = {
@ -29,22 +30,30 @@ export const getMinWidthByHeaderStyle = (headerStyle: HeaderStyle) => {
return `min-width: ${baseWidths[headerStyle]};`;
};
export const getDayLabel = (date: Date, headerStyle: HeaderStyle): string => {
const day = format(date, 'EEEE');
export const getDayLabel = (date: Date, headerStyle: HeaderStyle, locale: Locale = enUS): string => {
const day = format(date, 'EEEE', { locale });
switch (headerStyle) {
case 'expanded':
return day.toUpperCase();
case 'compacted':
return day.slice(0, 3).toUpperCase();
// Use EEE format for proper 3-letter abbreviation in the locale
return format(date, 'EEE', { locale }).toUpperCase();
case 'tiny':
if (day === 'Thursday') return 'T';
if (day === 'Tuesday') return 't';
if (day === 'Saturday') return 's';
if (day === 'Sunday') return 'S';
if (day === 'Monday') return 'M';
if (day === 'Wednesday') return 'W';
if (day === 'Friday') return 'F';
return day[0];
// Use EEEEE format for single letter abbreviation when available
// Fallback to first letter for better locale support
const singleLetter = format(date, 'EEEEE', { locale });
// For English, handle special cases (Thursday/Tuesday, Saturday/Sunday)
if (locale.code === 'en-US' || locale.code === 'en') {
const dayName = format(date, 'EEEE', { locale });
if (dayName === 'Thursday') return 'T';
if (dayName === 'Tuesday') return 't';
if (dayName === 'Saturday') return 's';
if (dayName === 'Sunday') return 'S';
}
return singleLetter.toUpperCase();
default:
return day;
}

@ -0,0 +1,135 @@
import React, { useState } from 'react';
import { Year } from '../components/calendar/Year';
import { Controls } from '../components/calendar/Controls';
import { HeaderStyle, MonthCutoffType, DaySize } from '../types/calendar';
import styles from './Examples.module.scss';
import { Locale } from 'date-fns';
import {
enUS,
es,
fr,
de,
it,
pt,
ja,
ko,
zhCN,
ru,
ar,
nl,
pl,
tr,
sv,
nb
} from 'date-fns/locale';
export const LocaleSupport: React.FC = () => {
const [dayHeaderStyle, setDayHeaderStyle] = useState<HeaderStyle>('tiny');
const [monthCutoff, setMonthCutoff] = useState<MonthCutoffType>('truncate');
const [size, setSize] = useState<DaySize>('l');
const [fontProportion, setFontProportion] = useState<number>(100);
const [magnify, setMagnify] = useState<boolean>(false);
const [selectedLocale, setSelectedLocale] = useState<string>('enUS');
const locales: Record<string, { locale: Locale; name: string }> = {
enUS: { locale: enUS, name: 'English (US)' },
es: { locale: es, name: 'Español' },
fr: { locale: fr, name: 'Français' },
de: { locale: de, name: 'Deutsch' },
it: { locale: it, name: 'Italiano' },
pt: { locale: pt, name: 'Português' },
ja: { locale: ja, name: '日本語' },
ko: { locale: ko, name: '한국어' },
zhCN: { locale: zhCN, name: '中文 (简体)' },
ru: { locale: ru, name: 'Русский' },
ar: { locale: ar, name: 'العربية' },
nl: { locale: nl, name: 'Nederlands' },
pl: { locale: pl, name: 'Polski' },
tr: { locale: tr, name: 'Türkçe' },
sv: { locale: sv, name: 'Svenska' },
nb: { locale: nb, name: 'Norsk' }
};
const activeDates = [
new Date(2025, 0, 1), // New Year's Day
new Date(2025, 3, 21), // Spring
new Date(2025, 6, 14), // Bastille Day / Summer
new Date(2025, 9, 31), // Halloween
new Date(2025, 11, 25) // Christmas
];
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>International Locale Support</h2>
<p className={styles.sectionDescription}>
The calendar supports multiple languages through the date-fns locale system.
Day names, month names, and date formats are automatically localized.
</p>
<div className={styles.controlRow}>
<label className={styles.control}>
Language:
<select
value={selectedLocale}
onChange={(e) => setSelectedLocale(e.target.value)}
style={{ marginLeft: '8px', padding: '4px 8px' }}
>
{Object.entries(locales).map(([key, { name }]) => (
<option key={key} value={key}>{name}</option>
))}
</select>
</label>
</div>
<Controls
headerStyle={dayHeaderStyle}
monthCutoff={monthCutoff}
size={size}
fontProportion={fontProportion}
magnify={magnify}
onHeaderStyleChange={setDayHeaderStyle}
onMonthCutoffChange={setMonthCutoff}
onSizeChange={setSize}
onFontProportionChange={setFontProportion}
onMagnifyChange={setMagnify}
/>
<div className={styles.demoContainer}>
<Year
year={2025}
dayHeaderStyle={dayHeaderStyle}
monthCutoff={monthCutoff}
weekendDays={[6, 0]}
size={size}
fontProportion={fontProportion}
magnify={magnify}
locale={locales[selectedLocale].locale}
activeDates={activeDates}
/>
</div>
<div className={styles.info}>
<h4>Localized Elements:</h4>
<ul>
<li>Month names (e.g., January, Janvier, Januar, )</li>
<li>Day names in headers (Monday, Lundi, Montag, )</li>
<li>Day abbreviations (Mon, Lun, Mo, )</li>
<li>Single letter days (M, L, M, )</li>
</ul>
<h4 style={{ marginTop: '20px' }}>Header Style Examples:</h4>
<ul>
<li><strong>Expanded:</strong> Full day names (e.g., MONDAY, LUNDI, MONTAG)</li>
<li><strong>Compacted:</strong> 3-letter abbreviations (e.g., MON, LUN, MO)</li>
<li><strong>Tiny:</strong> Single letters with special handling for conflicts</li>
<li><strong>None:</strong> No day headers, only numbers</li>
</ul>
<p style={{ marginTop: '20px', fontSize: '0.9em', color: '#666' }}>
Note: Some languages may show squares or incorrect characters if the required fonts
are not installed on your system (especially for Arabic, Chinese, Japanese, and Korean).
</p>
</div>
</section>
);
};

@ -1,3 +1,5 @@
import { Locale } from 'date-fns';
export type DateRange = {
startDate: Date | null;
endDate: Date | null;
@ -71,6 +73,7 @@ export interface YearProps {
activeColors?: ActiveColors;
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
}
export interface MonthProps {
@ -89,6 +92,7 @@ export interface MonthProps {
activeColors?: ActiveColors;
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
}
export interface WeekProps {
@ -106,6 +110,7 @@ export interface WeekProps {
activeColors?: ActiveColors;
activeDates?: Date[];
colorScheme?: ColorScheme;
locale?: Locale;
}
export interface DayProps {
@ -120,4 +125,5 @@ export interface DayProps {
magnify?: boolean;
activeColors?: ActiveColors;
colorScheme?: ColorScheme;
locale?: Locale;
}

Loading…
Cancel
Save