@stacksjs/components 0.2.93 → 0.2.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -4
- package/package.json +5 -6
- package/src/components/CodeBlock.stx +0 -54
- package/src/components/Footer.stx +0 -66
- package/src/components/Hero.stx +0 -25
- package/src/components/Installation.stx +0 -37
- package/src/components/index.ts +0 -4
- package/src/composables/index.ts +0 -4
- package/src/composables/useCopyCode.ts +0 -73
- package/src/composables/useDarkMode.ts +0 -100
- package/src/composables/useSEO.ts +0 -230
- package/src/composables/utilities/index.ts +0 -7
- package/src/composables/utilities/useClickOutside.ts +0 -72
- package/src/composables/utilities/useFocusTrap.ts +0 -159
- package/src/composables/utilities/useKeyboard.ts +0 -124
- package/src/composables/utilities/useLocalStorage.ts +0 -132
- package/src/composables/utilities/useMediaQuery.ts +0 -118
- package/src/index.ts +0 -99
- package/src/stx.d.ts +0 -4
- package/src/ui/accordion/Accordion.stx +0 -146
- package/src/ui/accordion/AccordionItem.stx +0 -37
- package/src/ui/accordion/index.ts +0 -47
- package/src/ui/audio/Audio.stx +0 -50
- package/src/ui/audio/README.md +0 -41
- package/src/ui/audio/index.ts +0 -13
- package/src/ui/auth/Login.stx +0 -161
- package/src/ui/auth/README.md +0 -142
- package/src/ui/auth/Signup.stx +0 -159
- package/src/ui/auth/TwoFactorChallenge.stx +0 -111
- package/src/ui/auth/index.ts +0 -32
- package/src/ui/avatar/Avatar.stx +0 -77
- package/src/ui/avatar/index.ts +0 -12
- package/src/ui/badge/Badge.stx +0 -62
- package/src/ui/badge/index.ts +0 -10
- package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
- package/src/ui/breadcrumb/index.ts +0 -15
- package/src/ui/button/Button.stx +0 -74
- package/src/ui/button/README.md +0 -80
- package/src/ui/button/index.ts +0 -85
- package/src/ui/calendar/Calendar.stx +0 -189
- package/src/ui/calendar/README.md +0 -254
- package/src/ui/calendar/index.ts +0 -14
- package/src/ui/card/Card.stx +0 -68
- package/src/ui/card/index.ts +0 -13
- package/src/ui/checkbox/Checkbox.stx +0 -86
- package/src/ui/checkbox/index.ts +0 -16
- package/src/ui/combobox/Combobox.stx +0 -96
- package/src/ui/combobox/ComboboxButton.stx +0 -25
- package/src/ui/combobox/ComboboxInput.stx +0 -32
- package/src/ui/combobox/ComboboxOption.stx +0 -49
- package/src/ui/combobox/ComboboxOptions.stx +0 -16
- package/src/ui/combobox/README.md +0 -150
- package/src/ui/combobox/index.ts +0 -40
- package/src/ui/command-palette/CommandPalette.stx +0 -81
- package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
- package/src/ui/command-palette/README.md +0 -42
- package/src/ui/command-palette/index.ts +0 -15
- package/src/ui/dialog/Dialog.stx +0 -41
- package/src/ui/dialog/DialogBackdrop.stx +0 -9
- package/src/ui/dialog/DialogDescription.stx +0 -11
- package/src/ui/dialog/DialogPanel.stx +0 -16
- package/src/ui/dialog/DialogTitle.stx +0 -11
- package/src/ui/dialog/README.md +0 -121
- package/src/ui/dialog/index.ts +0 -32
- package/src/ui/drawer/Drawer.stx +0 -120
- package/src/ui/drawer/README.md +0 -90
- package/src/ui/drawer/index.ts +0 -9
- package/src/ui/dropdown/Dropdown.stx +0 -82
- package/src/ui/dropdown/DropdownButton.stx +0 -29
- package/src/ui/dropdown/DropdownItem.stx +0 -35
- package/src/ui/dropdown/DropdownItems.stx +0 -18
- package/src/ui/dropdown/README.md +0 -114
- package/src/ui/dropdown/index.ts +0 -29
- package/src/ui/form/Form.stx +0 -95
- package/src/ui/form/index.ts +0 -67
- package/src/ui/heatmap/Heatmap.stx +0 -294
- package/src/ui/image/Image.stx +0 -165
- package/src/ui/image/README.md +0 -164
- package/src/ui/image/index.ts +0 -22
- package/src/ui/input/EmailInput.stx +0 -30
- package/src/ui/input/NumberInput.stx +0 -126
- package/src/ui/input/PasswordInput.stx +0 -143
- package/src/ui/input/SearchInput.stx +0 -44
- package/src/ui/input/TextInput.stx +0 -132
- package/src/ui/input/index.ts +0 -180
- package/src/ui/listbox/Listbox.stx +0 -101
- package/src/ui/listbox/ListboxButton.stx +0 -28
- package/src/ui/listbox/ListboxLabel.stx +0 -11
- package/src/ui/listbox/ListboxOption.stx +0 -49
- package/src/ui/listbox/ListboxOptions.stx +0 -16
- package/src/ui/listbox/README.md +0 -135
- package/src/ui/listbox/index.ts +0 -39
- package/src/ui/navigator/Navigator.stx +0 -113
- package/src/ui/navigator/README.md +0 -352
- package/src/ui/navigator/index.ts +0 -20
- package/src/ui/notification/Notification.stx +0 -117
- package/src/ui/notification/README.md +0 -114
- package/src/ui/notification/index.ts +0 -12
- package/src/ui/pagination/Pagination.stx +0 -150
- package/src/ui/pagination/index.ts +0 -11
- package/src/ui/payment/Checkout.stx +0 -31
- package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
- package/src/ui/payment/PaymentMethods.stx +0 -104
- package/src/ui/payment/README.md +0 -345
- package/src/ui/payment/SubscriptionCheckout.stx +0 -323
- package/src/ui/payment/index.ts +0 -59
- package/src/ui/popover/Popover.stx +0 -60
- package/src/ui/popover/PopoverButton.stx +0 -29
- package/src/ui/popover/PopoverPanel.stx +0 -23
- package/src/ui/popover/README.md +0 -91
- package/src/ui/popover/index.ts +0 -22
- package/src/ui/portal/Portal.stx +0 -57
- package/src/ui/portal/index.ts +0 -6
- package/src/ui/progress/Progress.stx +0 -131
- package/src/ui/progress/index.ts +0 -12
- package/src/ui/radio/Radio.stx +0 -77
- package/src/ui/radio/index.ts +0 -15
- package/src/ui/radio-group/README.md +0 -139
- package/src/ui/radio-group/RadioGroup.stx +0 -60
- package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
- package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
- package/src/ui/radio-group/RadioGroupOption.stx +0 -58
- package/src/ui/radio-group/index.ts +0 -30
- package/src/ui/select/README.md +0 -180
- package/src/ui/select/Select.stx +0 -82
- package/src/ui/select/index.ts +0 -21
- package/src/ui/sidebar/Sidebar.stx +0 -476
- package/src/ui/sidebar/SidebarFooter.stx +0 -71
- package/src/ui/sidebar/SidebarHeader.stx +0 -103
- package/src/ui/sidebar/SidebarItem.stx +0 -86
- package/src/ui/sidebar/SidebarSection.stx +0 -83
- package/src/ui/sidebar/index.ts +0 -153
- package/src/ui/sidebar/themes.ts +0 -240
- package/src/ui/skeleton/Skeleton.stx +0 -48
- package/src/ui/skeleton/index.ts +0 -11
- package/src/ui/spinner/Spinner.stx +0 -109
- package/src/ui/spinner/index.ts +0 -9
- package/src/ui/stepper/README.md +0 -36
- package/src/ui/stepper/Stepper.stx +0 -17
- package/src/ui/stepper/StepperStep.stx +0 -57
- package/src/ui/stepper/index.ts +0 -19
- package/src/ui/storage/README.md +0 -193
- package/src/ui/storage/index.ts +0 -347
- package/src/ui/switch/README.md +0 -114
- package/src/ui/switch/Switch.stx +0 -72
- package/src/ui/switch/index.ts +0 -10
- package/src/ui/table/README.md +0 -39
- package/src/ui/table/Table.stx +0 -14
- package/src/ui/table/TableBody.stx +0 -10
- package/src/ui/table/TableCell.stx +0 -10
- package/src/ui/table/TableHead.stx +0 -10
- package/src/ui/table/TableHeader.stx +0 -20
- package/src/ui/table/TableRow.stx +0 -11
- package/src/ui/table/index.ts +0 -35
- package/src/ui/tabs/TabPanel.stx +0 -25
- package/src/ui/tabs/Tabs.stx +0 -202
- package/src/ui/tabs/index.ts +0 -50
- package/src/ui/teleport/Teleport.stx +0 -65
- package/src/ui/teleport/index.ts +0 -7
- package/src/ui/textarea/Textarea.stx +0 -108
- package/src/ui/textarea/index.ts +0 -20
- package/src/ui/tooltip/Tooltip.stx +0 -71
- package/src/ui/tooltip/index.ts +0 -10
- package/src/ui/transition/README.md +0 -30
- package/src/ui/transition/Transition.stx +0 -102
- package/src/ui/transition/index.ts +0 -25
- package/src/ui/video/README.md +0 -47
- package/src/ui/video/Video.stx +0 -45
- package/src/ui/video/index.ts +0 -16
- package/src/ui/virtual-list/VirtualList.stx +0 -67
- package/src/ui/virtual-list/index.ts +0 -10
- package/src/ui/virtual-table/VirtualTable.stx +0 -103
- package/src/ui/virtual-table/index.ts +0 -21
- package/src/utils/PROP_VALIDATION.md +0 -458
- package/src/utils/THEME_SYSTEM.md +0 -702
- package/src/utils/accessibility.ts +0 -841
- package/src/utils/animation/easing.ts +0 -368
- package/src/utils/animation/helpers.ts +0 -223
- package/src/utils/animation/index.ts +0 -5
- package/src/utils/animation/keyframes.ts +0 -230
- package/src/utils/error-handling.ts +0 -475
- package/src/utils/highlighter.ts +0 -93
- package/src/utils/i18n.ts +0 -634
- package/src/utils/keep-alive.ts +0 -819
- package/src/utils/lazy-loading.ts +0 -530
- package/src/utils/prop-validation.ts +0 -478
- package/src/utils/theme.ts +0 -626
package/src/utils/highlighter.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import type { Highlighter as TSHighlighter } from 'ts-syntax-highlighter'
|
|
2
|
-
import { createHighlighter } from 'ts-syntax-highlighter'
|
|
3
|
-
|
|
4
|
-
let highlighterInstance: TSHighlighter | null = null
|
|
5
|
-
|
|
6
|
-
export interface HighlighterOptions {
|
|
7
|
-
theme?: 'light' | 'dark' | 'auto'
|
|
8
|
-
language?: string
|
|
9
|
-
lineNumbers?: boolean
|
|
10
|
-
wrapLines?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface HighlightResult {
|
|
14
|
-
html: string
|
|
15
|
-
language: string
|
|
16
|
-
theme: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Initialize or get the syntax highlighter instance
|
|
21
|
-
*/
|
|
22
|
-
export async function getHighlighter(): Promise<TSHighlighter> {
|
|
23
|
-
if (!highlighterInstance) {
|
|
24
|
-
highlighterInstance = await createHighlighter({
|
|
25
|
-
theme: 'github-light',
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
return highlighterInstance
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Highlight code with syntax highlighting
|
|
33
|
-
*/
|
|
34
|
-
export async function highlight(
|
|
35
|
-
code: string,
|
|
36
|
-
options: HighlighterOptions = {},
|
|
37
|
-
): Promise<HighlightResult> {
|
|
38
|
-
const {
|
|
39
|
-
theme = 'auto',
|
|
40
|
-
language = 'typescript',
|
|
41
|
-
lineNumbers: _lineNumbers = false,
|
|
42
|
-
wrapLines: _wrapLines = true,
|
|
43
|
-
} = options
|
|
44
|
-
|
|
45
|
-
const highlighter = await getHighlighter()
|
|
46
|
-
|
|
47
|
-
// Auto-detect theme based on system preference
|
|
48
|
-
const effectiveTheme = theme === 'auto'
|
|
49
|
-
? (globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'github-dark' : 'github-light')
|
|
50
|
-
: theme === 'dark' ? 'github-dark' : 'github-light'
|
|
51
|
-
|
|
52
|
-
const html = await highlighter.highlight(code, language)
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
html: typeof html === 'string' ? html : String(html),
|
|
56
|
-
language,
|
|
57
|
-
theme: effectiveTheme,
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Detect language from code content
|
|
63
|
-
*/
|
|
64
|
-
export function detectLanguage(code: string): string {
|
|
65
|
-
// Simple heuristics for language detection
|
|
66
|
-
if (code.includes('<!DOCTYPE') || code.includes('<html'))
|
|
67
|
-
return 'html'
|
|
68
|
-
if (code.includes('<?php'))
|
|
69
|
-
return 'php'
|
|
70
|
-
if (code.includes('import') && code.includes('from'))
|
|
71
|
-
return 'typescript'
|
|
72
|
-
if (code.includes('function') || code.includes('const'))
|
|
73
|
-
return 'javascript'
|
|
74
|
-
if (code.includes('{') && code.includes('}'))
|
|
75
|
-
return 'json'
|
|
76
|
-
if (code.includes('$') && code.includes('|'))
|
|
77
|
-
return 'bash'
|
|
78
|
-
if (code.includes('#') && code.includes('##'))
|
|
79
|
-
return 'markdown'
|
|
80
|
-
|
|
81
|
-
return 'typescript' // Default
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Apply headwind utility classes to highlighted code
|
|
86
|
-
*/
|
|
87
|
-
export function applyHeadwindClasses(html: string, additionalClasses?: string): string {
|
|
88
|
-
const baseClasses = 'rounded-md overflow-auto text-sm leading-relaxed font-mono'
|
|
89
|
-
const classes = additionalClasses ? `${baseClasses} ${additionalClasses}` : baseClasses
|
|
90
|
-
|
|
91
|
-
// Wrap the highlighted HTML with headwind classes
|
|
92
|
-
return `<div class="${classes}">${html}</div>`
|
|
93
|
-
}
|
package/src/utils/i18n.ts
DELETED
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internationalization (i18n) system for @stacksjs/components
|
|
3
|
-
*
|
|
4
|
-
* Provides translation, locale management, pluralization, and date/number formatting
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* import { createI18n, t } from '@stacksjs/components'
|
|
9
|
-
*
|
|
10
|
-
* // Create i18n instance
|
|
11
|
-
* const i18n = createI18n({
|
|
12
|
-
* locale: 'en',
|
|
13
|
-
* fallbackLocale: 'en',
|
|
14
|
-
* messages: {
|
|
15
|
-
* en: { welcome: 'Welcome' },
|
|
16
|
-
* es: { welcome: 'Bienvenido' }
|
|
17
|
-
* }
|
|
18
|
-
* })
|
|
19
|
-
*
|
|
20
|
-
* // Use translations
|
|
21
|
-
* t('welcome') // 'Welcome' or 'Bienvenido' based on locale
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Translation messages structure
|
|
27
|
-
*/
|
|
28
|
-
export type MessageValue = string | { [key: string]: MessageValue }
|
|
29
|
-
export type Messages = Record<string, MessageValue>
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Locale messages
|
|
33
|
-
*/
|
|
34
|
-
export type LocaleMessages = Record<string, Messages>
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Pluralization rules
|
|
38
|
-
*/
|
|
39
|
-
export type PluralRule = (_count: number, _locale: string) => number
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* I18n configuration
|
|
43
|
-
*/
|
|
44
|
-
export interface I18nConfig {
|
|
45
|
-
/** Current locale */
|
|
46
|
-
locale: string
|
|
47
|
-
/** Fallback locale when translation is missing */
|
|
48
|
-
fallbackLocale?: string
|
|
49
|
-
/** Translation messages for all locales */
|
|
50
|
-
messages: LocaleMessages
|
|
51
|
-
/** Custom pluralization rules */
|
|
52
|
-
pluralRules?: Record<string, PluralRule>
|
|
53
|
-
/** Date formatting options */
|
|
54
|
-
dateTimeFormats?: Record<string, Intl.DateTimeFormatOptions>
|
|
55
|
-
/** Number formatting options */
|
|
56
|
-
numberFormats?: Record<string, Intl.NumberFormatOptions>
|
|
57
|
-
/** Missing translation handler */
|
|
58
|
-
missing?: (locale: string, key: string) => string | void
|
|
59
|
-
/** Whether to warn about missing translations */
|
|
60
|
-
warnMissing?: boolean
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* I18n instance
|
|
65
|
-
*/
|
|
66
|
-
export interface I18n {
|
|
67
|
-
/** Current locale */
|
|
68
|
-
locale: string
|
|
69
|
-
/** Available locales */
|
|
70
|
-
availableLocales: string[]
|
|
71
|
-
/** Set current locale */
|
|
72
|
-
setLocale: (locale: string) => void
|
|
73
|
-
/** Get translation */
|
|
74
|
-
t: (key: string, params?: Record<string, any>) => string
|
|
75
|
-
/** Get pluralized translation */
|
|
76
|
-
tc: (key: string, count: number, params?: Record<string, any>) => string
|
|
77
|
-
/** Format date */
|
|
78
|
-
d: (date: Date | number, format?: string) => string
|
|
79
|
-
/** Format number */
|
|
80
|
-
n: (value: number, format?: string) => string
|
|
81
|
-
/** Check if translation exists */
|
|
82
|
-
te: (key: string) => boolean
|
|
83
|
-
/** Add locale messages */
|
|
84
|
-
addMessages: (locale: string, messages: Messages) => void
|
|
85
|
-
/** Remove locale */
|
|
86
|
-
removeLocale: (locale: string) => void
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Global i18n instance
|
|
91
|
-
*/
|
|
92
|
-
let globalI18n: I18n | null = null
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Default pluralization rule (English-like)
|
|
96
|
-
*/
|
|
97
|
-
const defaultPluralRule: PluralRule = (count: number) => {
|
|
98
|
-
return count === 1 ? 0 : 1
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get nested property from object by path
|
|
103
|
-
*/
|
|
104
|
-
function getNestedProperty(obj: any, path: string): any {
|
|
105
|
-
return path.split('.').reduce((current, key) => current?.[key], obj)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Interpolate parameters in translation string
|
|
110
|
-
*/
|
|
111
|
-
function interpolate(template: string, params: Record<string, any> = {}): string {
|
|
112
|
-
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
113
|
-
return params[key] !== undefined ? String(params[key]) : match
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Create an i18n instance
|
|
119
|
-
*
|
|
120
|
-
* @param config - I18n configuration
|
|
121
|
-
* @returns I18n instance
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* const i18n = createI18n({
|
|
126
|
-
* locale: 'en',
|
|
127
|
-
* fallbackLocale: 'en',
|
|
128
|
-
* messages: {
|
|
129
|
-
* en: {
|
|
130
|
-
* welcome: 'Welcome, {name}!',
|
|
131
|
-
* items: 'No items | One item | {count} items'
|
|
132
|
-
* },
|
|
133
|
-
* es: {
|
|
134
|
-
* welcome: 'Bienvenido, {name}!',
|
|
135
|
-
* items: 'Sin elementos | Un elemento | {count} elementos'
|
|
136
|
-
* }
|
|
137
|
-
* }
|
|
138
|
-
* })
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export function createI18n(config: I18nConfig): I18n {
|
|
142
|
-
let {
|
|
143
|
-
locale,
|
|
144
|
-
fallbackLocale = 'en',
|
|
145
|
-
messages,
|
|
146
|
-
pluralRules = {},
|
|
147
|
-
dateTimeFormats = {},
|
|
148
|
-
numberFormats = {},
|
|
149
|
-
missing,
|
|
150
|
-
warnMissing = true,
|
|
151
|
-
} = config
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get translation for key
|
|
155
|
-
*/
|
|
156
|
-
function t(key: string, params: Record<string, any> = {}): string {
|
|
157
|
-
// Try current locale
|
|
158
|
-
let translation = getNestedProperty(messages[locale], key)
|
|
159
|
-
|
|
160
|
-
// Try fallback locale
|
|
161
|
-
if (translation === undefined && fallbackLocale) {
|
|
162
|
-
translation = getNestedProperty(messages[fallbackLocale], key)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Handle missing translation
|
|
166
|
-
if (translation === undefined) {
|
|
167
|
-
if (missing) {
|
|
168
|
-
const result = missing(locale, key)
|
|
169
|
-
if (result) {
|
|
170
|
-
return interpolate(result, params)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (warnMissing && typeof console !== 'undefined') {
|
|
175
|
-
console.warn(`[i18n] Missing translation for key "${key}" in locale "${locale}"`)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return key
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Interpolate parameters
|
|
182
|
-
if (typeof translation === 'string') {
|
|
183
|
-
return interpolate(translation, params)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return String(translation)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Get pluralized translation
|
|
191
|
-
*/
|
|
192
|
-
function tc(key: string, count: number, params: Record<string, any> = {}): string {
|
|
193
|
-
const translation = t(key, { ...params, count })
|
|
194
|
-
|
|
195
|
-
// If translation contains plural forms separated by |
|
|
196
|
-
if (translation.includes('|')) {
|
|
197
|
-
const forms = translation.split('|').map(s => s.trim())
|
|
198
|
-
const pluralRule = pluralRules[locale] || defaultPluralRule
|
|
199
|
-
const index = pluralRule(count, locale)
|
|
200
|
-
|
|
201
|
-
const form = forms[index] || forms[forms.length - 1]
|
|
202
|
-
return interpolate(form, { ...params, count })
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return translation
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Format date
|
|
210
|
-
*/
|
|
211
|
-
function d(date: Date | number, format = 'default'): string {
|
|
212
|
-
const dateObj = typeof date === 'number' ? new Date(date) : date
|
|
213
|
-
const formatOptions = dateTimeFormats[format] || dateTimeFormats.default || {}
|
|
214
|
-
|
|
215
|
-
return new Intl.DateTimeFormat(locale, formatOptions).format(dateObj)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Format number
|
|
220
|
-
*/
|
|
221
|
-
function n(value: number, format = 'default'): string {
|
|
222
|
-
const formatOptions = numberFormats[format] || numberFormats.default || {}
|
|
223
|
-
|
|
224
|
-
return new Intl.NumberFormat(locale, formatOptions).format(value)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Check if translation exists
|
|
229
|
-
*/
|
|
230
|
-
function te(key: string): boolean {
|
|
231
|
-
const translation = getNestedProperty(messages[locale], key)
|
|
232
|
-
if (translation !== undefined) {
|
|
233
|
-
return true
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (fallbackLocale) {
|
|
237
|
-
const fallbackTranslation = getNestedProperty(messages[fallbackLocale], key)
|
|
238
|
-
return fallbackTranslation !== undefined
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return false
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Set locale
|
|
246
|
-
*/
|
|
247
|
-
function setLocale(newLocale: string): void {
|
|
248
|
-
if (!messages[newLocale]) {
|
|
249
|
-
console.warn(`[i18n] Locale "${newLocale}" not found. Available locales: ${Object.keys(messages).join(', ')}`)
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
locale = newLocale
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Add messages for a locale
|
|
257
|
-
*/
|
|
258
|
-
function addMessages(addLocale: string, newMessages: Messages): void {
|
|
259
|
-
if (!messages[addLocale]) {
|
|
260
|
-
messages[addLocale] = {}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Deep merge messages
|
|
264
|
-
messages[addLocale] = {
|
|
265
|
-
...messages[addLocale],
|
|
266
|
-
...newMessages,
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Remove a locale
|
|
272
|
-
*/
|
|
273
|
-
function removeLocale(removeLocale: string): void {
|
|
274
|
-
delete messages[removeLocale]
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return {
|
|
278
|
-
get locale() {
|
|
279
|
-
return locale
|
|
280
|
-
},
|
|
281
|
-
get availableLocales() {
|
|
282
|
-
return Object.keys(messages)
|
|
283
|
-
},
|
|
284
|
-
setLocale,
|
|
285
|
-
t,
|
|
286
|
-
tc,
|
|
287
|
-
d,
|
|
288
|
-
n,
|
|
289
|
-
te,
|
|
290
|
-
addMessages,
|
|
291
|
-
removeLocale,
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Set global i18n instance
|
|
297
|
-
*
|
|
298
|
-
* @param i18n - I18n instance
|
|
299
|
-
*/
|
|
300
|
-
export function setI18n(i18n: I18n): void {
|
|
301
|
-
globalI18n = i18n
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Get global i18n instance
|
|
306
|
-
*
|
|
307
|
-
* @returns Global i18n instance or null
|
|
308
|
-
*/
|
|
309
|
-
export function getI18n(): I18n | null {
|
|
310
|
-
return globalI18n
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Global translation function
|
|
315
|
-
* Uses global i18n instance
|
|
316
|
-
*
|
|
317
|
-
* @param key - Translation key
|
|
318
|
-
* @param params - Interpolation parameters
|
|
319
|
-
* @returns Translated string
|
|
320
|
-
*/
|
|
321
|
-
export function t(key: string, params?: Record<string, any>): string {
|
|
322
|
-
if (!globalI18n) {
|
|
323
|
-
console.warn('[i18n] No global i18n instance set. Call setI18n() first.')
|
|
324
|
-
return key
|
|
325
|
-
}
|
|
326
|
-
return globalI18n.t(key, params)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Global pluralization function
|
|
331
|
-
*
|
|
332
|
-
* @param key - Translation key
|
|
333
|
-
* @param count - Count for pluralization
|
|
334
|
-
* @param params - Interpolation parameters
|
|
335
|
-
* @returns Pluralized translated string
|
|
336
|
-
*/
|
|
337
|
-
export function tc(key: string, count: number, params?: Record<string, any>): string {
|
|
338
|
-
if (!globalI18n) {
|
|
339
|
-
console.warn('[i18n] No global i18n instance set. Call setI18n() first.')
|
|
340
|
-
return key
|
|
341
|
-
}
|
|
342
|
-
return globalI18n.tc(key, count, params)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Global date formatting function
|
|
347
|
-
*
|
|
348
|
-
* @param date - Date to format
|
|
349
|
-
* @param format - Format name
|
|
350
|
-
* @returns Formatted date string
|
|
351
|
-
*/
|
|
352
|
-
export function d(date: Date | number, format?: string): string {
|
|
353
|
-
if (!globalI18n) {
|
|
354
|
-
console.warn('[i18n] No global i18n instance set. Call setI18n() first.')
|
|
355
|
-
return String(date)
|
|
356
|
-
}
|
|
357
|
-
return globalI18n.d(date, format)
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Global number formatting function
|
|
362
|
-
*
|
|
363
|
-
* @param value - Number to format
|
|
364
|
-
* @param format - Format name
|
|
365
|
-
* @returns Formatted number string
|
|
366
|
-
*/
|
|
367
|
-
export function n(value: number, format?: string): string {
|
|
368
|
-
if (!globalI18n) {
|
|
369
|
-
console.warn('[i18n] No global i18n instance set. Call setI18n() first.')
|
|
370
|
-
return String(value)
|
|
371
|
-
}
|
|
372
|
-
return globalI18n.n(value, format)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Predefined pluralization rules for common languages
|
|
377
|
-
*/
|
|
378
|
-
export const pluralRules: Record<string, PluralRule> = {
|
|
379
|
-
// English, German, Dutch, Swedish, Danish, Norwegian, Finnish
|
|
380
|
-
en: count => count === 1 ? 0 : 1,
|
|
381
|
-
de: count => count === 1 ? 0 : 1,
|
|
382
|
-
nl: count => count === 1 ? 0 : 1,
|
|
383
|
-
sv: count => count === 1 ? 0 : 1,
|
|
384
|
-
|
|
385
|
-
// French, Brazilian Portuguese
|
|
386
|
-
fr: count => count === 0 || count === 1 ? 0 : 1,
|
|
387
|
-
pt_BR: count => count === 0 || count === 1 ? 0 : 1,
|
|
388
|
-
|
|
389
|
-
// Russian, Ukrainian, Serbian, Croatian
|
|
390
|
-
ru: (count) => {
|
|
391
|
-
if (count % 10 === 1 && count % 100 !== 11)
|
|
392
|
-
return 0
|
|
393
|
-
if (count % 10 >= 2 && count % 10 <= 4 && (count % 100 < 10 || count % 100 >= 20))
|
|
394
|
-
return 1
|
|
395
|
-
return 2
|
|
396
|
-
},
|
|
397
|
-
|
|
398
|
-
// Polish
|
|
399
|
-
pl: (count) => {
|
|
400
|
-
if (count === 1)
|
|
401
|
-
return 0
|
|
402
|
-
if (count % 10 >= 2 && count % 10 <= 4 && (count % 100 < 10 || count % 100 >= 20))
|
|
403
|
-
return 1
|
|
404
|
-
return 2
|
|
405
|
-
},
|
|
406
|
-
|
|
407
|
-
// Czech, Slovak
|
|
408
|
-
cs: (count) => {
|
|
409
|
-
if (count === 1)
|
|
410
|
-
return 0
|
|
411
|
-
if (count >= 2 && count <= 4)
|
|
412
|
-
return 1
|
|
413
|
-
return 2
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
// Arabic
|
|
417
|
-
ar: (count) => {
|
|
418
|
-
if (count === 0)
|
|
419
|
-
return 0
|
|
420
|
-
if (count === 1)
|
|
421
|
-
return 1
|
|
422
|
-
if (count === 2)
|
|
423
|
-
return 2
|
|
424
|
-
if (count % 100 >= 3 && count % 100 <= 10)
|
|
425
|
-
return 3
|
|
426
|
-
if (count % 100 >= 11)
|
|
427
|
-
return 4
|
|
428
|
-
return 5
|
|
429
|
-
},
|
|
430
|
-
|
|
431
|
-
// Japanese, Korean, Chinese, Thai, Vietnamese
|
|
432
|
-
ja: () => 0,
|
|
433
|
-
ko: () => 0,
|
|
434
|
-
zh: () => 0,
|
|
435
|
-
th: () => 0,
|
|
436
|
-
vi: () => 0,
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Common date/time format presets
|
|
441
|
-
*/
|
|
442
|
-
export const dateTimeFormats: Record<string, Intl.DateTimeFormatOptions> = {
|
|
443
|
-
short: {
|
|
444
|
-
year: 'numeric',
|
|
445
|
-
month: 'numeric',
|
|
446
|
-
day: 'numeric',
|
|
447
|
-
},
|
|
448
|
-
long: {
|
|
449
|
-
year: 'numeric',
|
|
450
|
-
month: 'long',
|
|
451
|
-
day: 'numeric',
|
|
452
|
-
},
|
|
453
|
-
full: {
|
|
454
|
-
weekday: 'long',
|
|
455
|
-
year: 'numeric',
|
|
456
|
-
month: 'long',
|
|
457
|
-
day: 'numeric',
|
|
458
|
-
},
|
|
459
|
-
time: {
|
|
460
|
-
hour: 'numeric',
|
|
461
|
-
minute: 'numeric',
|
|
462
|
-
},
|
|
463
|
-
datetime: {
|
|
464
|
-
year: 'numeric',
|
|
465
|
-
month: 'numeric',
|
|
466
|
-
day: 'numeric',
|
|
467
|
-
hour: 'numeric',
|
|
468
|
-
minute: 'numeric',
|
|
469
|
-
},
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Common number format presets
|
|
474
|
-
*/
|
|
475
|
-
export const numberFormats: Record<string, Intl.NumberFormatOptions> = {
|
|
476
|
-
decimal: {
|
|
477
|
-
style: 'decimal',
|
|
478
|
-
minimumFractionDigits: 2,
|
|
479
|
-
maximumFractionDigits: 2,
|
|
480
|
-
},
|
|
481
|
-
currency: {
|
|
482
|
-
style: 'currency',
|
|
483
|
-
currency: 'USD',
|
|
484
|
-
},
|
|
485
|
-
percent: {
|
|
486
|
-
style: 'percent',
|
|
487
|
-
minimumFractionDigits: 0,
|
|
488
|
-
maximumFractionDigits: 2,
|
|
489
|
-
},
|
|
490
|
-
compact: {
|
|
491
|
-
notation: 'compact',
|
|
492
|
-
},
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* RTL (Right-to-Left) language detection
|
|
497
|
-
*
|
|
498
|
-
* @param locale - Locale code
|
|
499
|
-
* @returns Whether locale is RTL
|
|
500
|
-
*/
|
|
501
|
-
export function isRTL(locale: string): boolean {
|
|
502
|
-
const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi']
|
|
503
|
-
const lang = locale.split('-')[0].toLowerCase()
|
|
504
|
-
return rtlLocales.includes(lang)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Load locale messages dynamically
|
|
509
|
-
*
|
|
510
|
-
* @param locale - Locale code
|
|
511
|
-
* @param loadFn - Function to load messages
|
|
512
|
-
* @returns Promise of messages
|
|
513
|
-
*
|
|
514
|
-
* @example
|
|
515
|
-
* ```ts
|
|
516
|
-
* const messages = await loadLocaleMessages('es', async () => {
|
|
517
|
-
* const module = await import('./locales/es.json')
|
|
518
|
-
* return module.default
|
|
519
|
-
* })
|
|
520
|
-
* ```
|
|
521
|
-
*/
|
|
522
|
-
export async function loadLocaleMessages(
|
|
523
|
-
locale: string,
|
|
524
|
-
loadFn: () => Promise<Messages>,
|
|
525
|
-
): Promise<Messages> {
|
|
526
|
-
try {
|
|
527
|
-
return await loadFn()
|
|
528
|
-
}
|
|
529
|
-
catch (error) {
|
|
530
|
-
console.error(`[i18n] Failed to load messages for locale "${locale}":`, error)
|
|
531
|
-
return {}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Component-specific i18n messages
|
|
537
|
-
*
|
|
538
|
-
* Provides default translations for component library
|
|
539
|
-
*/
|
|
540
|
-
export const componentMessages: LocaleMessages = {
|
|
541
|
-
en: {
|
|
542
|
-
common: {
|
|
543
|
-
loading: 'Loading...',
|
|
544
|
-
error: 'Error',
|
|
545
|
-
retry: 'Retry',
|
|
546
|
-
cancel: 'Cancel',
|
|
547
|
-
confirm: 'Confirm',
|
|
548
|
-
close: 'Close',
|
|
549
|
-
save: 'Save',
|
|
550
|
-
delete: 'Delete',
|
|
551
|
-
edit: 'Edit',
|
|
552
|
-
search: 'Search',
|
|
553
|
-
clear: 'Clear',
|
|
554
|
-
submit: 'Submit',
|
|
555
|
-
reset: 'Reset',
|
|
556
|
-
},
|
|
557
|
-
form: {
|
|
558
|
-
required: 'This field is required',
|
|
559
|
-
invalid: 'Invalid value',
|
|
560
|
-
tooShort: 'Value is too short (minimum {min} characters)',
|
|
561
|
-
tooLong: 'Value is too long (maximum {max} characters)',
|
|
562
|
-
invalidEmail: 'Invalid email address',
|
|
563
|
-
invalidUrl: 'Invalid URL',
|
|
564
|
-
},
|
|
565
|
-
pagination: {
|
|
566
|
-
previous: 'Previous',
|
|
567
|
-
next: 'Next',
|
|
568
|
-
page: 'Page {page}',
|
|
569
|
-
of: 'of {total}',
|
|
570
|
-
},
|
|
571
|
-
},
|
|
572
|
-
es: {
|
|
573
|
-
common: {
|
|
574
|
-
loading: 'Cargando...',
|
|
575
|
-
error: 'Error',
|
|
576
|
-
retry: 'Reintentar',
|
|
577
|
-
cancel: 'Cancelar',
|
|
578
|
-
confirm: 'Confirmar',
|
|
579
|
-
close: 'Cerrar',
|
|
580
|
-
save: 'Guardar',
|
|
581
|
-
delete: 'Eliminar',
|
|
582
|
-
edit: 'Editar',
|
|
583
|
-
search: 'Buscar',
|
|
584
|
-
clear: 'Limpiar',
|
|
585
|
-
submit: 'Enviar',
|
|
586
|
-
reset: 'Restablecer',
|
|
587
|
-
},
|
|
588
|
-
form: {
|
|
589
|
-
required: 'Este campo es obligatorio',
|
|
590
|
-
invalid: 'Valor inválido',
|
|
591
|
-
tooShort: 'El valor es demasiado corto (mínimo {min} caracteres)',
|
|
592
|
-
tooLong: 'El valor es demasiado largo (máximo {max} caracteres)',
|
|
593
|
-
invalidEmail: 'Dirección de correo electrónico inválida',
|
|
594
|
-
invalidUrl: 'URL inválida',
|
|
595
|
-
},
|
|
596
|
-
pagination: {
|
|
597
|
-
previous: 'Anterior',
|
|
598
|
-
next: 'Siguiente',
|
|
599
|
-
page: 'Página {page}',
|
|
600
|
-
of: 'de {total}',
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
fr: {
|
|
604
|
-
common: {
|
|
605
|
-
loading: 'Chargement...',
|
|
606
|
-
error: 'Erreur',
|
|
607
|
-
retry: 'Réessayer',
|
|
608
|
-
cancel: 'Annuler',
|
|
609
|
-
confirm: 'Confirmer',
|
|
610
|
-
close: 'Fermer',
|
|
611
|
-
save: 'Enregistrer',
|
|
612
|
-
delete: 'Supprimer',
|
|
613
|
-
edit: 'Modifier',
|
|
614
|
-
search: 'Rechercher',
|
|
615
|
-
clear: 'Effacer',
|
|
616
|
-
submit: 'Soumettre',
|
|
617
|
-
reset: 'Réinitialiser',
|
|
618
|
-
},
|
|
619
|
-
form: {
|
|
620
|
-
required: 'Ce champ est obligatoire',
|
|
621
|
-
invalid: 'Valeur invalide',
|
|
622
|
-
tooShort: 'La valeur est trop courte (minimum {min} caractères)',
|
|
623
|
-
tooLong: 'La valeur est trop longue (maximum {max} caractères)',
|
|
624
|
-
invalidEmail: 'Adresse e-mail invalide',
|
|
625
|
-
invalidUrl: 'URL invalide',
|
|
626
|
-
},
|
|
627
|
-
pagination: {
|
|
628
|
-
previous: 'Précédent',
|
|
629
|
-
next: 'Suivant',
|
|
630
|
-
page: 'Page {page}',
|
|
631
|
-
of: 'sur {total}',
|
|
632
|
-
},
|
|
633
|
-
},
|
|
634
|
-
}
|