@stacksjs/i18n 0.70.86 → 0.70.88
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/package.json +13 -4
- package/dist/bootstrap.d.ts +0 -14
- package/dist/formatter.d.ts +0 -105
- package/dist/index.d.ts +0 -94
- package/dist/index.js +0 -2
- package/dist/loader.d.ts +0 -52
- package/dist/locale-switch.d.ts +0 -16
- package/dist/pluralization.d.ts +0 -42
- package/dist/translator.d.ts +0 -79
- package/dist/types.d.ts +0 -213
- package/src/bootstrap.ts +0 -107
- package/src/formatter.ts +0 -399
- package/src/index.ts +0 -100
- package/src/loader.ts +0 -335
- package/src/locale-switch.ts +0 -81
- package/src/pluralization.ts +0 -290
- package/src/translator.ts +0 -502
- package/src/types.ts +0 -238
package/src/bootstrap.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { config } from '@stacksjs/config'
|
|
3
|
-
import { projectPath } from '@stacksjs/path'
|
|
4
|
-
import { loadFromDirectory } from './loader'
|
|
5
|
-
import { configure, getAvailableLocales, setLocale } from './translator'
|
|
6
|
-
|
|
7
|
-
let localesLoaded = false
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Load translation files from the project `locales/` directory once.
|
|
11
|
-
* Locale subdirectories (`locales/de.yml`, `locales/en.yml`, …) are detected
|
|
12
|
-
* automatically by `loadFromDirectory`.
|
|
13
|
-
*/
|
|
14
|
-
export async function ensureLocalesLoaded(): Promise<void> {
|
|
15
|
-
if (localesLoaded)
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
const directory = projectPath('locales')
|
|
19
|
-
if (existsSync(directory)) {
|
|
20
|
-
await loadFromDirectory({
|
|
21
|
-
directory,
|
|
22
|
-
extensions: ['.yaml', '.yml', '.json'],
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const app = (config as { app?: { locale?: string, fallbackLocale?: string, env?: string } }).app
|
|
27
|
-
const defaultLocale = app?.locale ?? 'en'
|
|
28
|
-
const fallback = app?.fallbackLocale ?? defaultLocale
|
|
29
|
-
const available = getAvailableLocales()
|
|
30
|
-
configure({
|
|
31
|
-
locale: defaultLocale,
|
|
32
|
-
fallbackLocale: fallback,
|
|
33
|
-
availableLocales: available.length > 0 ? available : [defaultLocale],
|
|
34
|
-
warnMissing: (app?.env ?? process.env.APP_ENV) !== 'production',
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
localesLoaded = true
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Resolve the active locale for an HTTP request.
|
|
42
|
-
* Order: STX locale prefix (`/en/...`), `?locale=`, `locale` cookie,
|
|
43
|
-
* Accept-Language, then `config.app.locale`.
|
|
44
|
-
*/
|
|
45
|
-
export function resolveRequestLocale(request?: Request): string {
|
|
46
|
-
const app = (config as { app?: { locale?: string, fallbackLocale?: string } }).app
|
|
47
|
-
const defaultLocale = app?.locale ?? 'en'
|
|
48
|
-
const available = new Set(getAvailableLocales())
|
|
49
|
-
if (!available.size)
|
|
50
|
-
available.add(defaultLocale)
|
|
51
|
-
|
|
52
|
-
const pick = (code: string): string => {
|
|
53
|
-
const normalized = code.trim().toLowerCase()
|
|
54
|
-
if (available.has(normalized))
|
|
55
|
-
return normalized
|
|
56
|
-
const short = normalized.split('-')[0]
|
|
57
|
-
if (short && available.has(short))
|
|
58
|
-
return short
|
|
59
|
-
return defaultLocale
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (!request)
|
|
63
|
-
return defaultLocale
|
|
64
|
-
|
|
65
|
-
const url = new URL(request.url)
|
|
66
|
-
const pathname = url.pathname
|
|
67
|
-
|
|
68
|
-
const fromStx = request.headers.get('x-stx-locale')
|
|
69
|
-
if (fromStx)
|
|
70
|
-
return pick(fromStx)
|
|
71
|
-
|
|
72
|
-
// Non-default locales are served under `/<code>/…` by stx-serve i18n routing.
|
|
73
|
-
for (const code of available) {
|
|
74
|
-
if (code === defaultLocale)
|
|
75
|
-
continue
|
|
76
|
-
if (pathname === `/${code}` || pathname === `/${code}/`)
|
|
77
|
-
return pick(code)
|
|
78
|
-
if (pathname.startsWith(`/${code}/`))
|
|
79
|
-
return pick(code)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const fromQuery = url.searchParams.get('locale')
|
|
83
|
-
if (fromQuery)
|
|
84
|
-
return pick(fromQuery)
|
|
85
|
-
|
|
86
|
-
const cookie = request.headers.get('cookie') ?? ''
|
|
87
|
-
const match = cookie.match(/(?:^|;\s*)locale=([a-z]{2}(?:-[a-z]{2})?)(?:;|$)/i)
|
|
88
|
-
if (match?.[1])
|
|
89
|
-
return pick(match[1])
|
|
90
|
-
|
|
91
|
-
const accept = request.headers.get('accept-language') ?? ''
|
|
92
|
-
for (const part of accept.split(',')) {
|
|
93
|
-
const tag = part.split(';')[0]?.trim()
|
|
94
|
-
if (tag)
|
|
95
|
-
return pick(tag)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return defaultLocale
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Load `locales/` and activate the global translator for this request. */
|
|
102
|
-
export async function applyRequestLocale(request?: Request): Promise<string> {
|
|
103
|
-
await ensureLocalesLoaded()
|
|
104
|
-
const locale = resolveRequestLocale(request)
|
|
105
|
-
setLocale(locale)
|
|
106
|
-
return locale
|
|
107
|
-
}
|
package/src/formatter.ts
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internationalized Formatting
|
|
3
|
-
*
|
|
4
|
-
* Provides locale-aware formatting for dates, numbers, currencies, and more.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { getLocale } from './translator'
|
|
8
|
-
|
|
9
|
-
// =============================================================================
|
|
10
|
-
// Date Formatting
|
|
11
|
-
// =============================================================================
|
|
12
|
-
|
|
13
|
-
export type DateStyle = 'full' | 'long' | 'medium' | 'short'
|
|
14
|
-
export type TimeStyle = 'full' | 'long' | 'medium' | 'short'
|
|
15
|
-
|
|
16
|
-
export interface DateFormatOptions extends Intl.DateTimeFormatOptions {
|
|
17
|
-
dateStyle?: DateStyle
|
|
18
|
-
timeStyle?: TimeStyle
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Format a date
|
|
23
|
-
*/
|
|
24
|
-
export function formatDate(
|
|
25
|
-
date: Date | number | string,
|
|
26
|
-
style: DateStyle | DateFormatOptions = 'medium',
|
|
27
|
-
locale?: string,
|
|
28
|
-
): string {
|
|
29
|
-
const targetLocale = locale || getLocale()
|
|
30
|
-
const dateObj = toDate(date)
|
|
31
|
-
|
|
32
|
-
const options: Intl.DateTimeFormatOptions = typeof style === 'string'
|
|
33
|
-
? { dateStyle: style }
|
|
34
|
-
: style
|
|
35
|
-
|
|
36
|
-
return new Intl.DateTimeFormat(targetLocale, options).format(dateObj)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Format a time
|
|
41
|
-
*/
|
|
42
|
-
export function formatTime(
|
|
43
|
-
date: Date | number | string,
|
|
44
|
-
style: TimeStyle | DateFormatOptions = 'short',
|
|
45
|
-
locale?: string,
|
|
46
|
-
): string {
|
|
47
|
-
const targetLocale = locale || getLocale()
|
|
48
|
-
const dateObj = toDate(date)
|
|
49
|
-
|
|
50
|
-
const options: Intl.DateTimeFormatOptions = typeof style === 'string'
|
|
51
|
-
? { timeStyle: style }
|
|
52
|
-
: style
|
|
53
|
-
|
|
54
|
-
return new Intl.DateTimeFormat(targetLocale, options).format(dateObj)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Format a date and time
|
|
59
|
-
*/
|
|
60
|
-
export function formatDateTime(
|
|
61
|
-
date: Date | number | string,
|
|
62
|
-
dateStyle: DateStyle = 'medium',
|
|
63
|
-
timeStyle: TimeStyle = 'short',
|
|
64
|
-
locale?: string,
|
|
65
|
-
): string {
|
|
66
|
-
const targetLocale = locale || getLocale()
|
|
67
|
-
const dateObj = toDate(date)
|
|
68
|
-
|
|
69
|
-
return new Intl.DateTimeFormat(targetLocale, {
|
|
70
|
-
dateStyle,
|
|
71
|
-
timeStyle,
|
|
72
|
-
}).format(dateObj)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Format relative time (e.g., "2 days ago", "in 3 hours")
|
|
77
|
-
*/
|
|
78
|
-
export function formatRelativeTime(
|
|
79
|
-
date: Date | number | string,
|
|
80
|
-
baseDate: Date | number | string = new Date(),
|
|
81
|
-
style: 'long' | 'short' | 'narrow' = 'long',
|
|
82
|
-
locale?: string,
|
|
83
|
-
): string {
|
|
84
|
-
const targetLocale = locale || getLocale()
|
|
85
|
-
const dateObj = toDate(date)
|
|
86
|
-
const baseDateObj = toDate(baseDate)
|
|
87
|
-
|
|
88
|
-
const diffMs = dateObj.getTime() - baseDateObj.getTime()
|
|
89
|
-
const diffSeconds = Math.round(diffMs / 1000)
|
|
90
|
-
const diffMinutes = Math.round(diffSeconds / 60)
|
|
91
|
-
const diffHours = Math.round(diffMinutes / 60)
|
|
92
|
-
const diffDays = Math.round(diffHours / 24)
|
|
93
|
-
const diffWeeks = Math.round(diffDays / 7)
|
|
94
|
-
const diffMonths = Math.round(diffDays / 30)
|
|
95
|
-
const diffYears = Math.round(diffDays / 365)
|
|
96
|
-
|
|
97
|
-
const rtf = new Intl.RelativeTimeFormat(targetLocale, { style })
|
|
98
|
-
|
|
99
|
-
// Choose appropriate unit
|
|
100
|
-
if (Math.abs(diffSeconds) < 60) {
|
|
101
|
-
return rtf.format(diffSeconds, 'second')
|
|
102
|
-
}
|
|
103
|
-
if (Math.abs(diffMinutes) < 60) {
|
|
104
|
-
return rtf.format(diffMinutes, 'minute')
|
|
105
|
-
}
|
|
106
|
-
if (Math.abs(diffHours) < 24) {
|
|
107
|
-
return rtf.format(diffHours, 'hour')
|
|
108
|
-
}
|
|
109
|
-
if (Math.abs(diffDays) < 7) {
|
|
110
|
-
return rtf.format(diffDays, 'day')
|
|
111
|
-
}
|
|
112
|
-
if (Math.abs(diffWeeks) < 4) {
|
|
113
|
-
return rtf.format(diffWeeks, 'week')
|
|
114
|
-
}
|
|
115
|
-
if (Math.abs(diffMonths) < 12) {
|
|
116
|
-
return rtf.format(diffMonths, 'month')
|
|
117
|
-
}
|
|
118
|
-
return rtf.format(diffYears, 'year')
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// =============================================================================
|
|
122
|
-
// Number Formatting
|
|
123
|
-
// =============================================================================
|
|
124
|
-
|
|
125
|
-
export interface NumberFormatOptions extends Intl.NumberFormatOptions {
|
|
126
|
-
compact?: boolean
|
|
127
|
-
precision?: number
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Format a number
|
|
132
|
-
*/
|
|
133
|
-
export function formatNumber(
|
|
134
|
-
value: number,
|
|
135
|
-
options: NumberFormatOptions = {},
|
|
136
|
-
locale?: string,
|
|
137
|
-
): string {
|
|
138
|
-
const targetLocale = locale || getLocale()
|
|
139
|
-
|
|
140
|
-
const formatOptions: Intl.NumberFormatOptions = { ...options }
|
|
141
|
-
|
|
142
|
-
if (options.compact) {
|
|
143
|
-
formatOptions.notation = 'compact'
|
|
144
|
-
formatOptions.compactDisplay = 'short'
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (options.precision !== undefined) {
|
|
148
|
-
formatOptions.minimumFractionDigits = options.precision
|
|
149
|
-
formatOptions.maximumFractionDigits = options.precision
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return new Intl.NumberFormat(targetLocale, formatOptions).format(value)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Format a currency value
|
|
157
|
-
*/
|
|
158
|
-
export function formatCurrency(
|
|
159
|
-
value: number,
|
|
160
|
-
currency = 'USD',
|
|
161
|
-
options: Omit<NumberFormatOptions, 'style' | 'currency'> = {},
|
|
162
|
-
locale?: string,
|
|
163
|
-
): string {
|
|
164
|
-
const targetLocale = locale || getLocale()
|
|
165
|
-
|
|
166
|
-
return new Intl.NumberFormat(targetLocale, {
|
|
167
|
-
style: 'currency',
|
|
168
|
-
currency,
|
|
169
|
-
...options,
|
|
170
|
-
}).format(value)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Format a percentage
|
|
175
|
-
*/
|
|
176
|
-
export function formatPercent(
|
|
177
|
-
value: number,
|
|
178
|
-
options: Omit<NumberFormatOptions, 'style'> = {},
|
|
179
|
-
locale?: string,
|
|
180
|
-
): string {
|
|
181
|
-
const targetLocale = locale || getLocale()
|
|
182
|
-
|
|
183
|
-
return new Intl.NumberFormat(targetLocale, {
|
|
184
|
-
style: 'percent',
|
|
185
|
-
...options,
|
|
186
|
-
}).format(value)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Format a number with units (e.g., "5 kilograms", "10 miles")
|
|
191
|
-
*/
|
|
192
|
-
export function formatUnit(
|
|
193
|
-
value: number,
|
|
194
|
-
unit: string,
|
|
195
|
-
unitDisplay: 'long' | 'short' | 'narrow' = 'short',
|
|
196
|
-
locale?: string,
|
|
197
|
-
): string {
|
|
198
|
-
const targetLocale = locale || getLocale()
|
|
199
|
-
|
|
200
|
-
return new Intl.NumberFormat(targetLocale, {
|
|
201
|
-
style: 'unit',
|
|
202
|
-
unit,
|
|
203
|
-
unitDisplay,
|
|
204
|
-
}).format(value)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Format bytes to human-readable format
|
|
209
|
-
*/
|
|
210
|
-
export function formatBytes(
|
|
211
|
-
bytes: number,
|
|
212
|
-
decimals = 2,
|
|
213
|
-
locale?: string,
|
|
214
|
-
): string {
|
|
215
|
-
const targetLocale = locale || getLocale()
|
|
216
|
-
|
|
217
|
-
if (bytes === 0) return '0 B'
|
|
218
|
-
|
|
219
|
-
const k = 1024
|
|
220
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
221
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
222
|
-
|
|
223
|
-
const value = bytes / Math.pow(k, i)
|
|
224
|
-
const formatted = new Intl.NumberFormat(targetLocale, {
|
|
225
|
-
minimumFractionDigits: 0,
|
|
226
|
-
maximumFractionDigits: decimals,
|
|
227
|
-
}).format(value)
|
|
228
|
-
|
|
229
|
-
return `${formatted} ${sizes[i]}`
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// =============================================================================
|
|
233
|
-
// List Formatting
|
|
234
|
-
// =============================================================================
|
|
235
|
-
|
|
236
|
-
export type ListType = 'conjunction' | 'disjunction' | 'unit'
|
|
237
|
-
export type ListStyle = 'long' | 'short' | 'narrow'
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Format a list of items
|
|
241
|
-
*/
|
|
242
|
-
export function formatList(
|
|
243
|
-
items: string[],
|
|
244
|
-
type: ListType = 'conjunction',
|
|
245
|
-
style: ListStyle = 'long',
|
|
246
|
-
locale?: string,
|
|
247
|
-
): string {
|
|
248
|
-
const targetLocale = locale || getLocale()
|
|
249
|
-
|
|
250
|
-
return new Intl.ListFormat(targetLocale, { type, style }).format(items)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// =============================================================================
|
|
254
|
-
// Plural Formatting
|
|
255
|
-
// =============================================================================
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get the plural category for a number
|
|
259
|
-
*/
|
|
260
|
-
export function formatPlural(
|
|
261
|
-
value: number,
|
|
262
|
-
locale?: string,
|
|
263
|
-
): Intl.LDMLPluralRule {
|
|
264
|
-
const targetLocale = locale || getLocale()
|
|
265
|
-
const pr = new Intl.PluralRules(targetLocale)
|
|
266
|
-
return pr.select(value)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Select from plural options based on a number
|
|
271
|
-
*/
|
|
272
|
-
export function selectPlural<T>(
|
|
273
|
-
value: number,
|
|
274
|
-
options: Partial<Record<Intl.LDMLPluralRule, T>> & { other: T },
|
|
275
|
-
locale?: string,
|
|
276
|
-
): T {
|
|
277
|
-
const category = formatPlural(value, locale)
|
|
278
|
-
return options[category] ?? options.other
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// =============================================================================
|
|
282
|
-
// Display Names
|
|
283
|
-
// =============================================================================
|
|
284
|
-
|
|
285
|
-
export type DisplayNameType = 'language' | 'region' | 'script' | 'currency' | 'calendar' | 'dateTimeField'
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Get display name for a code
|
|
289
|
-
*/
|
|
290
|
-
export function getDisplayName(
|
|
291
|
-
code: string,
|
|
292
|
-
type: DisplayNameType = 'language',
|
|
293
|
-
style: 'long' | 'short' | 'narrow' = 'long',
|
|
294
|
-
locale?: string,
|
|
295
|
-
): string | undefined {
|
|
296
|
-
const targetLocale = locale || getLocale()
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
const dn = new Intl.DisplayNames(targetLocale, { type, style })
|
|
300
|
-
return dn.of(code)
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return undefined
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Get language name
|
|
309
|
-
*/
|
|
310
|
-
export function getLanguageName(
|
|
311
|
-
languageCode: string,
|
|
312
|
-
locale?: string,
|
|
313
|
-
): string | undefined {
|
|
314
|
-
return getDisplayName(languageCode, 'language', 'long', locale)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Get region/country name
|
|
319
|
-
*/
|
|
320
|
-
export function getRegionName(
|
|
321
|
-
regionCode: string,
|
|
322
|
-
locale?: string,
|
|
323
|
-
): string | undefined {
|
|
324
|
-
return getDisplayName(regionCode, 'region', 'long', locale)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Get currency name
|
|
329
|
-
*/
|
|
330
|
-
export function getCurrencyName(
|
|
331
|
-
currencyCode: string,
|
|
332
|
-
locale?: string,
|
|
333
|
-
): string | undefined {
|
|
334
|
-
return getDisplayName(currencyCode, 'currency', 'long', locale)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// =============================================================================
|
|
338
|
-
// Collation
|
|
339
|
-
// =============================================================================
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Compare strings in a locale-aware manner
|
|
343
|
-
*/
|
|
344
|
-
export function compareStrings(
|
|
345
|
-
a: string,
|
|
346
|
-
b: string,
|
|
347
|
-
options: Intl.CollatorOptions = {},
|
|
348
|
-
locale?: string,
|
|
349
|
-
): number {
|
|
350
|
-
const targetLocale = locale || getLocale()
|
|
351
|
-
const collator = new Intl.Collator(targetLocale, options)
|
|
352
|
-
return collator.compare(a, b)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Sort an array of strings in locale-aware order
|
|
357
|
-
*/
|
|
358
|
-
export function sortStrings(
|
|
359
|
-
strings: string[],
|
|
360
|
-
options: Intl.CollatorOptions = {},
|
|
361
|
-
locale?: string,
|
|
362
|
-
): string[] {
|
|
363
|
-
const targetLocale = locale || getLocale()
|
|
364
|
-
const collator = new Intl.Collator(targetLocale, options)
|
|
365
|
-
return [...strings].sort((a, b) => collator.compare(a, b))
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// =============================================================================
|
|
369
|
-
// Helpers
|
|
370
|
-
// =============================================================================
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Convert various date formats to Date object
|
|
374
|
-
*/
|
|
375
|
-
function toDate(date: Date | number | string): Date {
|
|
376
|
-
if (date instanceof Date) return date
|
|
377
|
-
if (typeof date === 'number') return new Date(date)
|
|
378
|
-
return new Date(date)
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Get the text direction for a locale
|
|
383
|
-
*/
|
|
384
|
-
export function getTextDirection(locale?: string): 'ltr' | 'rtl' {
|
|
385
|
-
const targetLocale = locale || getLocale()
|
|
386
|
-
const lang = (targetLocale.split('-')[0] ?? targetLocale).toLowerCase()
|
|
387
|
-
|
|
388
|
-
// RTL languages
|
|
389
|
-
const rtlLanguages = ['ar', 'he', 'fa', 'ur', 'yi', 'ps', 'sd', 'ug', 'ku', 'ckb']
|
|
390
|
-
|
|
391
|
-
return rtlLanguages.includes(lang) ? 'rtl' : 'ltr'
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Check if a locale uses RTL text direction
|
|
396
|
-
*/
|
|
397
|
-
export function isRTL(locale?: string): boolean {
|
|
398
|
-
return getTextDirection(locale) === 'rtl'
|
|
399
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stacks i18n - Internationalization System
|
|
3
|
-
*
|
|
4
|
-
* A comprehensive internationalization system with support for:
|
|
5
|
-
* - Translation loading from JSON/YAML files
|
|
6
|
-
* - Plural forms
|
|
7
|
-
* - Variable interpolation
|
|
8
|
-
* - Nested translations
|
|
9
|
-
* - Date/time formatting
|
|
10
|
-
* - Number formatting
|
|
11
|
-
* - Currency formatting
|
|
12
|
-
* - Relative time formatting
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { t, setLocale, addTranslations } from '@stacksjs/i18n'
|
|
17
|
-
*
|
|
18
|
-
* // Add translations
|
|
19
|
-
* addTranslations('en', {
|
|
20
|
-
* greeting: 'Hello, {name}!',
|
|
21
|
-
* items: '{count} item | {count} items',
|
|
22
|
-
* })
|
|
23
|
-
*
|
|
24
|
-
* // Use translations
|
|
25
|
-
* t('greeting', { name: 'World' }) // "Hello, World!"
|
|
26
|
-
* t('items', { count: 1 }) // "1 item"
|
|
27
|
-
* t('items', { count: 5 }) // "5 items"
|
|
28
|
-
*
|
|
29
|
-
* // Format dates and numbers
|
|
30
|
-
* formatDate(new Date(), 'long') // "January 8, 2026"
|
|
31
|
-
* formatNumber(1234567.89) // "1,234,567.89"
|
|
32
|
-
* formatCurrency(99.99, 'USD') // "$99.99"
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
export * from './translator'
|
|
37
|
-
export * from './formatter'
|
|
38
|
-
export * from './pluralization'
|
|
39
|
-
export * from './loader'
|
|
40
|
-
export * from './types'
|
|
41
|
-
|
|
42
|
-
// Re-export the file-loading and type-generation surface from
|
|
43
|
-
// `@stacksjs/ts-i18n`. The library handles disk loading (YAML / TS /
|
|
44
|
-
// JSON), namespace resolution, and `.d.ts` codegen for translation
|
|
45
|
-
// keys; the Stacks-side translator/formatter sits on top of that
|
|
46
|
-
// data with locale management, ICU pluralization, and Intl-backed
|
|
47
|
-
// formatters. Importing both names from `@stacksjs/i18n` keeps the
|
|
48
|
-
// app side from caring which symbol comes from which package.
|
|
49
|
-
export {
|
|
50
|
-
loadTranslations as loadTranslationsFromDisk,
|
|
51
|
-
generateTypes as generateI18nTypes,
|
|
52
|
-
generateTypesFromModule as generateI18nTypesFromModule,
|
|
53
|
-
generateSampleConfig as generateI18nSampleConfig,
|
|
54
|
-
writeOutputs as writeI18nOutputs,
|
|
55
|
-
createTranslator as createSimpleTranslator,
|
|
56
|
-
} from '@stacksjs/ts-i18n'
|
|
57
|
-
export type { I18nConfig as TsI18nConfig, TranslationTree, TranslationValue } from '@stacksjs/ts-i18n'
|
|
58
|
-
|
|
59
|
-
// Re-export main functions for convenience
|
|
60
|
-
export {
|
|
61
|
-
t,
|
|
62
|
-
trans,
|
|
63
|
-
tc,
|
|
64
|
-
te,
|
|
65
|
-
tm,
|
|
66
|
-
setLocale,
|
|
67
|
-
getLocale,
|
|
68
|
-
addTranslations,
|
|
69
|
-
loadTranslations,
|
|
70
|
-
getAvailableLocales,
|
|
71
|
-
hasTranslation,
|
|
72
|
-
I18n,
|
|
73
|
-
createI18n,
|
|
74
|
-
useI18n,
|
|
75
|
-
} from './translator'
|
|
76
|
-
|
|
77
|
-
export {
|
|
78
|
-
ensureLocalesLoaded,
|
|
79
|
-
resolveRequestLocale,
|
|
80
|
-
applyRequestLocale,
|
|
81
|
-
} from './bootstrap'
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
createLocaleSwitchResponse,
|
|
85
|
-
localizePath,
|
|
86
|
-
stripLocalePrefix,
|
|
87
|
-
type LocaleSwitchConfig,
|
|
88
|
-
} from './locale-switch'
|
|
89
|
-
|
|
90
|
-
export {
|
|
91
|
-
formatDate,
|
|
92
|
-
formatTime,
|
|
93
|
-
formatDateTime,
|
|
94
|
-
formatNumber,
|
|
95
|
-
formatCurrency,
|
|
96
|
-
formatPercent,
|
|
97
|
-
formatRelativeTime,
|
|
98
|
-
formatList,
|
|
99
|
-
formatPlural,
|
|
100
|
-
} from './formatter'
|