@userfrosting/sprinkle-core 6.0.0-alpha.2 → 6.0.0-alpha.3
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/app/assets/composables/usePageMeta.ts +4 -2
- package/app/assets/index.d.ts +9 -0
- package/app/assets/index.ts +15 -4
- package/app/assets/interfaces/DictionaryApi.ts +25 -0
- package/app/assets/interfaces/index.ts +1 -0
- package/app/assets/stores/Helpers/PluralRules.ts +218 -0
- package/app/assets/stores/index.ts +1 -0
- package/app/assets/stores/useTranslator.ts +293 -0
- package/app/assets/tests/plugin.test.ts +10 -1
- package/app/assets/tests/stores/Helpers/PluralRules.test.ts +440 -0
- package/app/assets/tests/stores/useTranslator.test.ts +373 -0
- package/package.json +4 -2
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, expect, beforeEach, test, vi } from 'vitest'
|
|
2
|
+
import { setActivePinia, createPinia } from 'pinia'
|
|
3
|
+
import { DateTime, Settings } from 'luxon'
|
|
4
|
+
import axios from 'axios'
|
|
5
|
+
import { useTranslator } from '../../stores/useTranslator'
|
|
6
|
+
import type { DictionaryConfig, DictionaryEntries, DictionaryResponse } from '../../interfaces'
|
|
7
|
+
|
|
8
|
+
const testDictionaryConfig: DictionaryConfig = {
|
|
9
|
+
name: 'English',
|
|
10
|
+
regional: 'English',
|
|
11
|
+
authors: ['UserFrosting'],
|
|
12
|
+
plural_rule: 1,
|
|
13
|
+
dates: 'en-US'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const testDictionaryConfigFr: DictionaryConfig = {
|
|
17
|
+
name: 'French',
|
|
18
|
+
regional: 'Français',
|
|
19
|
+
authors: ['Malou'],
|
|
20
|
+
plural_rule: 1,
|
|
21
|
+
dates: 'fr-FR'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const testDictionaryEntries: DictionaryEntries = {
|
|
25
|
+
YES: 'Yes',
|
|
26
|
+
NO: 'No',
|
|
27
|
+
WELCOME_TO: 'Welcome to {{title}}!',
|
|
28
|
+
'ERROR.@TRANSLATION': 'The Error',
|
|
29
|
+
'ERROR.TITLE': "We've sensed a great disturbance in the Force.",
|
|
30
|
+
USERNAME: 'Username',
|
|
31
|
+
'COLOR.BLACK': 'black',
|
|
32
|
+
'COLOR.RED': 'red',
|
|
33
|
+
'COLOR.WHITE': 'white',
|
|
34
|
+
'COLOR.0': 'colors',
|
|
35
|
+
'COLOR.1': 'color',
|
|
36
|
+
'COLOR.2': 'colors',
|
|
37
|
+
'X_CARS.0': 'no cars',
|
|
38
|
+
'X_CARS.1': 'a car',
|
|
39
|
+
'X_CARS.2': '{{plural}} cars',
|
|
40
|
+
'X_HUNGRY_CATS.@PLURAL': 'num',
|
|
41
|
+
'X_HUNGRY_CATS.1': '{{num}} hungry cat',
|
|
42
|
+
'X_HUNGRY_CATS.2': '{{num}} hungry cats',
|
|
43
|
+
X_FOO: '{{plural}}x foos',
|
|
44
|
+
'CAR.1': 'car',
|
|
45
|
+
'CAR.2': 'cars',
|
|
46
|
+
'CAR.GAS': 'gas',
|
|
47
|
+
'CAR.EV.@TRANSLATION': 'electric',
|
|
48
|
+
'CAR.EV.FULL': 'full electric',
|
|
49
|
+
'CAR.FULL_MODEL': '{{make}} {{model}} {{year}}',
|
|
50
|
+
'MY_CARS.@TRANSLATION': 'My cars',
|
|
51
|
+
'MY_CARS.1': 'I have a {{type}} {{&CAR}}',
|
|
52
|
+
'MY_CARS.2': 'I have {{plural}} {{type}} {{&CAR}}',
|
|
53
|
+
TEST_LIMIT: 'Your test must be between {{min}} and {{max}} potatoes.',
|
|
54
|
+
MIN: 'minimum',
|
|
55
|
+
'X_RULES.0': 'no rules',
|
|
56
|
+
'X_RULES.1': '{{plural}} rule', // No plural form for 2
|
|
57
|
+
'X_BANANAS.0': 'no bananas', // No plural form for 1, 2, etc.
|
|
58
|
+
'X_DOGS.5': 'five dogs',
|
|
59
|
+
'X_DOGS.101': '101 Dalmatians',
|
|
60
|
+
'X_DOGS.1000': 'An island of dogs'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const testDictionary: DictionaryResponse = {
|
|
64
|
+
identifier: 'en_US',
|
|
65
|
+
config: testDictionaryConfig,
|
|
66
|
+
dictionary: testDictionaryEntries
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const testDictionaryFr: DictionaryResponse = {
|
|
70
|
+
identifier: 'fr_FR',
|
|
71
|
+
config: testDictionaryConfigFr,
|
|
72
|
+
dictionary: testDictionaryEntries
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('API Tests', async () => {
|
|
76
|
+
test('API should be saved', async () => {
|
|
77
|
+
// Arrange
|
|
78
|
+
setActivePinia(createPinia())
|
|
79
|
+
const translator = useTranslator()
|
|
80
|
+
const { translate, load } = translator
|
|
81
|
+
const response = { data: testDictionary }
|
|
82
|
+
vi.spyOn(axios, 'get').mockResolvedValue(response as any)
|
|
83
|
+
|
|
84
|
+
// Assert initial state
|
|
85
|
+
expect(translator.identifier).toBe('')
|
|
86
|
+
expect(translator.config).toEqual({
|
|
87
|
+
name: '',
|
|
88
|
+
regional: '',
|
|
89
|
+
authors: [],
|
|
90
|
+
plural_rule: 0,
|
|
91
|
+
dates: ''
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Act
|
|
95
|
+
await load()
|
|
96
|
+
|
|
97
|
+
// Assert config data
|
|
98
|
+
expect(axios.get).toHaveBeenCalledWith('/api/dictionary')
|
|
99
|
+
expect(translator.config).toEqual(testDictionaryConfig)
|
|
100
|
+
expect(translator.identifier).toBe('en_US')
|
|
101
|
+
|
|
102
|
+
// Assert basic translate method
|
|
103
|
+
expect(translate('YES')).toBe('Yes')
|
|
104
|
+
expect(translate('NO')).toBe('No')
|
|
105
|
+
expect(translate('WELCOME_TO')).toBe('Welcome to {{title}}!')
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('Translator Tests', () => {
|
|
110
|
+
beforeEach(async () => {
|
|
111
|
+
// Arrange
|
|
112
|
+
setActivePinia(createPinia())
|
|
113
|
+
const translator = useTranslator()
|
|
114
|
+
const { load } = translator
|
|
115
|
+
const response = { data: testDictionary }
|
|
116
|
+
vi.spyOn(axios, 'get').mockResolvedValue(response as any)
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
await load()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('Should handle basic translation', () => {
|
|
123
|
+
const { translate } = useTranslator()
|
|
124
|
+
expect(translate('USERNAME')).toBe('Username')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('Should handle key not existing', () => {
|
|
128
|
+
const { translate } = useTranslator()
|
|
129
|
+
expect(translate('NOT_EXIST')).toBe('NOT_EXIST')
|
|
130
|
+
expect(translate('NOT EXIST')).toBe('NOT EXIST')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('Should handle @TRANSLATION', () => {
|
|
134
|
+
const { translate } = useTranslator()
|
|
135
|
+
expect(translate('ERROR')).toBe('The Error')
|
|
136
|
+
expect(translate('ERROR.TITLE')).toBe("We've sensed a great disturbance in the Force.")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('Should handle placeholders', () => {
|
|
140
|
+
const { translate } = useTranslator()
|
|
141
|
+
expect(translate('WELCOME_TO')).toBe('Welcome to {{title}}!') // Placeholder not replaced
|
|
142
|
+
expect(translate('WELCOME_TO', { title: 'UserFrosting' })).toBe('Welcome to UserFrosting!')
|
|
143
|
+
expect(translate('WELCOME_TO', { bar: 'UserFrosting' })).toBe('Welcome to {{title}}!') // Wrong key
|
|
144
|
+
expect(translate('WELCOME_TO', 'UserFrosting')).toBe('Welcome to {{title}}!') // Key not provided
|
|
145
|
+
expect(translate('WELCOME_TO', { title: 'UserFrosting', foo: 'bar' })).toBe(
|
|
146
|
+
'Welcome to UserFrosting!'
|
|
147
|
+
) // Extra key not used
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('Should handle basic plurals', () => {
|
|
151
|
+
const { translate } = useTranslator()
|
|
152
|
+
expect(translate('X_CARS', 0)).toBe('no cars')
|
|
153
|
+
expect(translate('X_CARS', 1)).toBe('a car')
|
|
154
|
+
expect(translate('X_CARS', 2)).toBe('2 cars')
|
|
155
|
+
expect(translate('X_CARS', 5)).toBe('5 cars')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('Should handle pluralization with custom plural key', () => {
|
|
159
|
+
const { translate } = useTranslator()
|
|
160
|
+
expect(translate('X_HUNGRY_CATS', { num: 0 })).toBe('0 hungry cats')
|
|
161
|
+
expect(translate('X_HUNGRY_CATS', { num: 1 })).toBe('1 hungry cat')
|
|
162
|
+
expect(translate('X_HUNGRY_CATS', { num: 2 })).toBe('2 hungry cats')
|
|
163
|
+
expect(translate('X_HUNGRY_CATS', { num: 5 })).toBe('5 hungry cats')
|
|
164
|
+
|
|
165
|
+
// Custom key can also be omitted in the placeholder if it's the only
|
|
166
|
+
// placeholder even with custom plural key
|
|
167
|
+
expect(translate('X_HUNGRY_CATS', 5)).toBe('5 hungry cats')
|
|
168
|
+
|
|
169
|
+
// Test missing pluralization and placeholder (default to 1)
|
|
170
|
+
expect(translate('X_HUNGRY_CATS')).toBe('1 hungry cat')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('Should handle plurals default, when no placeholder', () => {
|
|
174
|
+
const { translate } = useTranslator()
|
|
175
|
+
expect(translate('X_CARS')).toBe('a car')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('Should handle Key With No Plural', () => {
|
|
179
|
+
const { translate } = useTranslator()
|
|
180
|
+
expect(translate('USERNAME', 123)).toBe('Username') // USERNAME has no placeholders
|
|
181
|
+
expect(translate('X_FOO')).toBe('{{plural}}x foos') // 'X_FOO' doesn't have children, so it's not treated as a "pluralize-able" string
|
|
182
|
+
expect(translate('X_FOO', { plural: 1 })).toBe('1x foos') // Replace {{plural}} with 1
|
|
183
|
+
expect(translate('X_FOO', 1)).toBe('1x foos') // Replace {{plural}} with 1, without specifying the key
|
|
184
|
+
expect(translate('X_FOO', 123)).toBe('123x foos') // Replace {{plural}} with 123
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('Should handle plurals for different plural rules', () => {
|
|
188
|
+
const { translate } = useTranslator()
|
|
189
|
+
|
|
190
|
+
// English plural rule is 1
|
|
191
|
+
expect(translate('COLOR', 0)).toBe('colors')
|
|
192
|
+
expect(translate('COLOR', 1)).toBe('color')
|
|
193
|
+
expect(translate('COLOR', 2)).toBe('colors')
|
|
194
|
+
expect(translate('COLOR', 3)).toBe('colors')
|
|
195
|
+
|
|
196
|
+
// Same as above, but with a custom plural key (french)
|
|
197
|
+
// Note "0" is plural (colors) in english, singular (couleur) in french !
|
|
198
|
+
// TODO : Implement plurals & load custom locale
|
|
199
|
+
// expect(translate('COLOR', 0)).toBe('colors')
|
|
200
|
+
// expect(translate('COLOR', 1)).toBe('color')
|
|
201
|
+
// expect(translate('COLOR', 2)).toBe('colors')
|
|
202
|
+
// expect(translate('COLOR', 3)).toBe('colors')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('Should handle a simple replacement when the key is not defined in dictionary', () => {
|
|
206
|
+
const { translate } = useTranslator()
|
|
207
|
+
expect(translate('You are {{status}}', { status: 'dumb' })).toBe('You are dumb')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('Should handle @TRANSLATION when the key have plural rules', () => {
|
|
211
|
+
const { translate } = useTranslator()
|
|
212
|
+
expect(translate('MY_CARS')).toBe('My cars') // @TRANSLATION is used, not the 1 rule
|
|
213
|
+
expect(translate('MY_CARS', 1)).toBe('I have a {{type}} car')
|
|
214
|
+
expect(translate('MY_CARS', 2)).toBe('I have 2 {{type}} cars')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('Should handle complex placeholders', () => {
|
|
218
|
+
const { translate } = useTranslator()
|
|
219
|
+
expect(translate('MY_CARS', { type: 'car' })).toBe('My cars')
|
|
220
|
+
expect(translate('MY_CARS', { type: 'gaz', plural: 1 })).toBe('I have a gaz car')
|
|
221
|
+
expect(translate('MY_CARS', { type: 'gaz', plural: 2 })).toBe('I have 2 gaz cars')
|
|
222
|
+
expect(translate('MY_CARS', { type: '&CAR.GAS', plural: 2 })).toBe('I have 2 gas cars')
|
|
223
|
+
expect(translate('MY_CARS', { type: '&CAR.EV', plural: 2 })).toBe('I have 2 electric cars')
|
|
224
|
+
expect(translate('MY_CARS', { type: '&CAR.EV.FULL', plural: 1 })).toBe(
|
|
225
|
+
'I have a full electric car'
|
|
226
|
+
)
|
|
227
|
+
expect(
|
|
228
|
+
translate('MY_CARS', {
|
|
229
|
+
type: '&CAR.FULL_MODEL',
|
|
230
|
+
plural: 5,
|
|
231
|
+
make: 'Toyota',
|
|
232
|
+
model: 'Camry',
|
|
233
|
+
year: 2022
|
|
234
|
+
})
|
|
235
|
+
).toBe('I have 5 Toyota Camry 2022 cars')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// Test basic placeholder replacement using int as placeholder value (So they don't try to translate "min" and "max")
|
|
239
|
+
// We don't want to end up with "Your test must be between _minimum_ and 200 potatoes"
|
|
240
|
+
test('Should handle placeholder not overwritten by other key', () => {
|
|
241
|
+
const { translate } = useTranslator()
|
|
242
|
+
expect(translate('TEST_LIMIT', { min: 4, max: 200 })).toBe(
|
|
243
|
+
'Your test must be between 4 and 200 potatoes.'
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// 2 will return singular as the plural is not defined
|
|
248
|
+
test('Should handle pluralization with no rules', () => {
|
|
249
|
+
const { translate } = useTranslator()
|
|
250
|
+
expect(translate('X_RULES', 0)).toBe('no rules')
|
|
251
|
+
expect(translate('X_RULES', 1)).toBe('1 rule')
|
|
252
|
+
expect(translate('X_RULES', 2)).toBe('2 rule')
|
|
253
|
+
|
|
254
|
+
// X_BANANAS
|
|
255
|
+
expect(translate('X_BANANAS', 0)).toBe('no bananas')
|
|
256
|
+
expect(translate('X_BANANAS', 1)).toBe('no bananas')
|
|
257
|
+
expect(translate('X_BANANAS', 2)).toBe('no bananas')
|
|
258
|
+
expect(translate('X_BANANAS', 5)).toBe('no bananas')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// The keys are int, but don't follow the rules. It will fallback to the literal key
|
|
262
|
+
test("Should handle plurals who doesn't follow the rules", () => {
|
|
263
|
+
const { translate } = useTranslator()
|
|
264
|
+
expect(translate('X_DOGS')).toBe('X_DOGS')
|
|
265
|
+
expect(translate('X_DOGS', 0)).toBe('X_DOGS')
|
|
266
|
+
expect(translate('X_DOGS', 1)).toBe('X_DOGS')
|
|
267
|
+
expect(translate('X_DOGS', 2)).toBe('X_DOGS') // No plural rules found
|
|
268
|
+
expect(translate('X_DOGS', 5)).toBe('five dogs') // This one is hardcoded and will fallback as normal string key
|
|
269
|
+
expect(translate('X_DOGS', 101)).toBe('101 Dalmatians') // Same here
|
|
270
|
+
expect(translate('X_DOGS', 102)).toBe('X_DOGS') // This one is not hardcoded
|
|
271
|
+
expect(translate('X_DOGS', 1000)).toBe('An island of dogs') // Still fallback, if the key is a string representing and INT
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe('Date Tests', async () => {
|
|
276
|
+
test('translateDate should return the correct values in English', async () => {
|
|
277
|
+
// Arrange
|
|
278
|
+
setActivePinia(createPinia())
|
|
279
|
+
const translator = useTranslator()
|
|
280
|
+
const { translateDate, load, getDateTime } = translator
|
|
281
|
+
const response = { data: testDictionary }
|
|
282
|
+
vi.spyOn(axios, 'get').mockResolvedValue(response as any)
|
|
283
|
+
await load()
|
|
284
|
+
|
|
285
|
+
// Assert basic translate method
|
|
286
|
+
// Force the default timezone, so the test is consistent regardless of
|
|
287
|
+
// the timezone of the runner
|
|
288
|
+
Settings.defaultZone = 'America/New_York'
|
|
289
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z')).toBe('Sun, Feb 2, 2025, 9:42 AM')
|
|
290
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z', 'DDD')).toBe('February 2, 2025')
|
|
291
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z', DateTime.DATETIME_MED)).toBe(
|
|
292
|
+
'Feb 2, 2025, 9:42 AM'
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
// Test get the Datetime object
|
|
296
|
+
expect(getDateTime('2025-02-02T14:42:12.000000Z').monthLong).toBe('February')
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('translateDate should return the correct values in French', async () => {
|
|
300
|
+
// Arrange
|
|
301
|
+
setActivePinia(createPinia())
|
|
302
|
+
const translator = useTranslator()
|
|
303
|
+
const { translateDate, load, getDateTime } = translator
|
|
304
|
+
const response = { data: testDictionaryFr }
|
|
305
|
+
vi.spyOn(axios, 'get').mockResolvedValue(response as any)
|
|
306
|
+
await load()
|
|
307
|
+
|
|
308
|
+
// Assert basic translate method
|
|
309
|
+
// Force the default timezone, so the test is consistent regardless of
|
|
310
|
+
// the timezone of the runner
|
|
311
|
+
Settings.defaultZone = 'America/New_York'
|
|
312
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z')).toBe('dim. 2 févr. 2025, 09:42')
|
|
313
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z', 'DDD')).toBe('2 février 2025')
|
|
314
|
+
expect(translateDate('2025-02-02T14:42:12.000000Z', DateTime.DATETIME_MED)).toBe(
|
|
315
|
+
'2 févr. 2025, 09:42'
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
// Test get the Datetime object
|
|
319
|
+
expect(getDateTime('2025-02-02T14:42:12.000000Z').monthLong).toBe('février')
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
describe('Plural Form Tests', () => {
|
|
324
|
+
beforeEach(async () => {
|
|
325
|
+
// Arrange
|
|
326
|
+
setActivePinia(createPinia())
|
|
327
|
+
const translator = useTranslator()
|
|
328
|
+
const { load } = translator
|
|
329
|
+
const response = { data: testDictionary }
|
|
330
|
+
vi.spyOn(axios, 'get').mockResolvedValue(response as any)
|
|
331
|
+
|
|
332
|
+
// Act
|
|
333
|
+
await load()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
test('Should return correct plural form for rule 1', () => {
|
|
337
|
+
const { getPluralForm } = useTranslator()
|
|
338
|
+
expect(getPluralForm(0, 1)).toBe(2)
|
|
339
|
+
expect(getPluralForm(1, 1)).toBe(1)
|
|
340
|
+
expect(getPluralForm(2, 1)).toBe(2)
|
|
341
|
+
expect(getPluralForm(20, 1)).toBe(2)
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test('Should return correct plural form for rule 2', () => {
|
|
345
|
+
const { getPluralForm } = useTranslator()
|
|
346
|
+
expect(getPluralForm(0, 2)).toBe(1) // French = zero is singular
|
|
347
|
+
expect(getPluralForm(1, 2)).toBe(1)
|
|
348
|
+
expect(getPluralForm(2, 2)).toBe(2)
|
|
349
|
+
expect(getPluralForm(20, 2)).toBe(2)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test('Should throw error for invalid rule', () => {
|
|
353
|
+
const { getPluralForm } = useTranslator()
|
|
354
|
+
expect(() => getPluralForm(1, 16)).toThrow('The rule number 16 must be between 0 and 15')
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('Should use default rule if forceRule is not provided', () => {
|
|
358
|
+
const { getPluralForm } = useTranslator()
|
|
359
|
+
expect(getPluralForm(0)).toBe(2)
|
|
360
|
+
expect(getPluralForm(1)).toBe(1)
|
|
361
|
+
expect(getPluralForm(2)).toBe(2)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('Should used the specified rule if forceRule is provided', () => {
|
|
365
|
+
const { getPluralForm } = useTranslator()
|
|
366
|
+
expect(getPluralForm(0, 1)).toBe(2) // English
|
|
367
|
+
expect(getPluralForm(0, 2)).toBe(1) // French
|
|
368
|
+
expect(getPluralForm(5, 1)).toBe(2) // English
|
|
369
|
+
expect(getPluralForm(5, 8)).toBe(3) // Slavic
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
// TODO : Test each plural rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@userfrosting/sprinkle-core",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core Sprinkle for UserFrosting",
|
|
6
6
|
"funding": "https://opencollective.com/userfrosting",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"app/assets/"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"dot-prop": "^9.0.0"
|
|
36
|
+
"dot-prop": "^9.0.0",
|
|
37
|
+
"luxon": "^3.5.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"axios": "^1.5.0",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@rushstack/eslint-patch": "^1.8.0",
|
|
46
47
|
"@tsconfig/node20": "^20.1.4",
|
|
48
|
+
"@types/luxon": "^3.4.2",
|
|
47
49
|
"@types/node": "^20.12.5",
|
|
48
50
|
"@vitejs/plugin-vue": "^5.0.4",
|
|
49
51
|
"@vitest/coverage-v8": "^1.6.0",
|