feat: Add comprehensive internationalization (i18n) support
- Add multi-language support with i18next and react-i18next - Implement user-selectable date format preferences (5 formats) - Add time format preferences (12-hour/24-hour) - Implement RTL (right-to-left) support for Arabic, Hebrew, Persian, Urdu - Add first day of week preference - Create LocalizationContext for managing i18n settings - Add LocalizationSettings component to Settings page - Include sample translations: English, Spanish, Arabic - Configure 10 languages: en, es, fr, de, ar, he, zh, ja, pt, ru - Add translation management documentation and Crowdin config - Create date formatting utilities that respect user preferences - Add RTL CSS styles for proper layout mirroring - Update Settings and ThemeToggle components to use translations - Add comprehensive documentation (implementation guide, quick reference, translation guide) All user preferences are persisted to localStorage and apply throughout the app.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { AlertsProvider } from '../service/AlertsProvider'
|
||||
import { NotificationProvider } from '../service/NotificationProvider'
|
||||
import { LocalizationProvider } from './LocalizationContext'
|
||||
import QueryContext from './QueryContext'
|
||||
import RouterContext from './RouterContext'
|
||||
import ThemeContext from './ThemeContext'
|
||||
@@ -8,6 +9,7 @@ const Contexts = ({ children }) => {
|
||||
const contexts = [
|
||||
AlertsProvider,
|
||||
ThemeContext,
|
||||
LocalizationProvider,
|
||||
QueryContext,
|
||||
NotificationProvider,
|
||||
RouterContext,
|
||||
|
||||
119
src/contexts/LocalizationContext.jsx
Normal file
119
src/contexts/LocalizationContext.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import useStickyState from '@/hooks/useStickyState'
|
||||
import moment from 'moment'
|
||||
import { createContext, useContext, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const LocalizationContext = createContext()
|
||||
|
||||
export const DATE_FORMATS = {
|
||||
MDY: 'MM/DD/YYYY',
|
||||
DMY: 'DD/MM/YYYY',
|
||||
YMD: 'YYYY-MM-DD',
|
||||
LONG: 'MMMM D, YYYY',
|
||||
SHORT: 'MMM D, YYYY',
|
||||
}
|
||||
|
||||
export const TIME_FORMATS = {
|
||||
HOUR_12: 'h:mm A',
|
||||
HOUR_24: 'HH:mm',
|
||||
}
|
||||
|
||||
export const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur']
|
||||
|
||||
export const AVAILABLE_LANGUAGES = [
|
||||
{ code: 'en', name: 'English', nativeName: 'English' },
|
||||
{ code: 'es', name: 'Spanish', nativeName: 'Español' },
|
||||
{ code: 'fr', name: 'French', nativeName: 'Français' },
|
||||
{ code: 'de', name: 'German', nativeName: 'Deutsch' },
|
||||
{ code: 'ar', name: 'Arabic', nativeName: 'العربية' },
|
||||
{ code: 'he', name: 'Hebrew', nativeName: 'עברית' },
|
||||
{ code: 'zh', name: 'Chinese', nativeName: '中文' },
|
||||
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
|
||||
{ code: 'pt', name: 'Portuguese', nativeName: 'Português' },
|
||||
{ code: 'ru', name: 'Russian', nativeName: 'Русский' },
|
||||
]
|
||||
|
||||
export const LocalizationProvider = ({ children }) => {
|
||||
const { i18n } = useTranslation()
|
||||
const [dateFormat, setDateFormat] = useStickyState(
|
||||
DATE_FORMATS.MDY,
|
||||
'dateFormat',
|
||||
)
|
||||
const [timeFormat, setTimeFormat] = useStickyState(
|
||||
TIME_FORMATS.HOUR_12,
|
||||
'timeFormat',
|
||||
)
|
||||
const [firstDayOfWeek, setFirstDayOfWeek] = useStickyState(0, 'firstDayOfWeek') // 0 = Sunday, 1 = Monday
|
||||
const [language, setLanguage] = useStickyState('en', 'language')
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language)
|
||||
moment.locale(language)
|
||||
}, [language, i18n])
|
||||
|
||||
useEffect(() => {
|
||||
const isRTL = RTL_LANGUAGES.includes(language)
|
||||
document.documentElement.dir = isRTL ? 'rtl' : 'ltr'
|
||||
document.documentElement.lang = language
|
||||
}, [language])
|
||||
|
||||
const formatDate = (date, format = dateFormat) => {
|
||||
if (!date) return ''
|
||||
return moment(date).format(format)
|
||||
}
|
||||
|
||||
const formatDateTime = (date, format) => {
|
||||
if (!date) return ''
|
||||
const dateTimeFormat = format || `${dateFormat} ${timeFormat}`
|
||||
return moment(date).format(dateTimeFormat)
|
||||
}
|
||||
|
||||
const formatTime = (date, format = timeFormat) => {
|
||||
if (!date) return ''
|
||||
return moment(date).format(format)
|
||||
}
|
||||
|
||||
const formatRelative = date => {
|
||||
if (!date) return ''
|
||||
return moment(date).fromNow()
|
||||
}
|
||||
|
||||
const formatCalendar = date => {
|
||||
if (!date) return ''
|
||||
return moment(date).calendar()
|
||||
}
|
||||
|
||||
const isRTL = RTL_LANGUAGES.includes(language)
|
||||
|
||||
const value = {
|
||||
dateFormat,
|
||||
setDateFormat,
|
||||
timeFormat,
|
||||
setTimeFormat,
|
||||
firstDayOfWeek,
|
||||
setFirstDayOfWeek,
|
||||
language,
|
||||
setLanguage,
|
||||
isRTL,
|
||||
formatDate,
|
||||
formatDateTime,
|
||||
formatTime,
|
||||
formatRelative,
|
||||
formatCalendar,
|
||||
availableLanguages: AVAILABLE_LANGUAGES,
|
||||
}
|
||||
|
||||
return (
|
||||
<LocalizationContext.Provider value={value}>
|
||||
{children}
|
||||
</LocalizationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useLocalization = () => {
|
||||
const context = useContext(LocalizationContext)
|
||||
if (!context) {
|
||||
throw new Error('useLocalization must be used within LocalizationProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
Reference in New Issue
Block a user