@userfrosting/sprinkle-core 6.0.0-alpha.1 → 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/index.ts +1 -0
- package/app/assets/composables/sprunjer.ts +24 -7
- package/app/assets/composables/usePageMeta.ts +114 -0
- package/app/assets/index.d.ts +9 -0
- package/app/assets/index.ts +15 -4
- package/app/assets/interfaces/ApiResponse.ts +8 -0
- package/app/assets/interfaces/DictionaryApi.ts +25 -0
- package/app/assets/interfaces/index.ts +27 -1
- package/app/assets/interfaces/sprunjer.ts +35 -2
- package/app/assets/interfaces/sprunjerApi.ts +33 -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
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { ref, toValue, watchEffect, computed } from 'vue'
|
|
35
35
|
import axios from 'axios'
|
|
36
|
-
import type { AssociativeArray, Sprunjer } from '../interfaces'
|
|
36
|
+
import type { AssociativeArray, Sprunjer, SprunjerData, SprunjerResponse } from '../interfaces'
|
|
37
37
|
|
|
38
38
|
export const useSprunjer = (
|
|
39
|
-
dataUrl:
|
|
39
|
+
dataUrl: string | (() => string),
|
|
40
40
|
defaultSorts: AssociativeArray = {},
|
|
41
41
|
defaultFilters: AssociativeArray = {},
|
|
42
42
|
defaultSize: number = 10,
|
|
@@ -48,8 +48,15 @@ export const useSprunjer = (
|
|
|
48
48
|
const sorts = ref<AssociativeArray>(defaultSorts)
|
|
49
49
|
const filters = ref<AssociativeArray>(defaultFilters)
|
|
50
50
|
|
|
51
|
-
// Raw data
|
|
52
|
-
const data = ref<
|
|
51
|
+
// Raw data - Init with default data
|
|
52
|
+
const data = ref<SprunjerData>({
|
|
53
|
+
count: 0,
|
|
54
|
+
count_filtered: 0,
|
|
55
|
+
rows: [],
|
|
56
|
+
listable: {},
|
|
57
|
+
sortable: [],
|
|
58
|
+
filterable: []
|
|
59
|
+
})
|
|
53
60
|
|
|
54
61
|
// State
|
|
55
62
|
const loading = ref<boolean>(false)
|
|
@@ -60,7 +67,7 @@ export const useSprunjer = (
|
|
|
60
67
|
async function fetch() {
|
|
61
68
|
loading.value = true
|
|
62
69
|
axios
|
|
63
|
-
.get(toValue(dataUrl), {
|
|
70
|
+
.get<SprunjerResponse>(toValue(dataUrl), {
|
|
64
71
|
params: {
|
|
65
72
|
size: size.value,
|
|
66
73
|
page: page.value,
|
|
@@ -69,13 +76,23 @@ export const useSprunjer = (
|
|
|
69
76
|
}
|
|
70
77
|
})
|
|
71
78
|
.then((response) => {
|
|
72
|
-
data
|
|
73
|
-
|
|
79
|
+
// Assign the response data to the Sprunje Data.
|
|
80
|
+
// Note both object can't be assigned directly, as the response
|
|
81
|
+
// object is not a SprunjeData object.
|
|
82
|
+
data.value.count = response.data.count
|
|
83
|
+
data.value.count_filtered = response.data.count_filtered
|
|
84
|
+
data.value.rows = response.data.rows
|
|
85
|
+
data.value.listable = response.data.listable ?? {}
|
|
86
|
+
data.value.sortable = response.data.sortable ?? []
|
|
87
|
+
data.value.filterable = response.data.filterable ?? []
|
|
74
88
|
})
|
|
75
89
|
.catch((err) => {
|
|
76
90
|
// TODO : User toast alert, or export alert
|
|
77
91
|
console.error(err)
|
|
78
92
|
})
|
|
93
|
+
.finally(() => {
|
|
94
|
+
loading.value = false
|
|
95
|
+
})
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
/**
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { computed, ref, watch } from 'vue'
|
|
2
|
+
import { useRoute } from 'vue-router'
|
|
3
|
+
import { useTranslator } from '@userfrosting/sprinkle-core/stores'
|
|
4
|
+
import { useConfigStore } from '../stores'
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Page Meta Composable
|
|
9
|
+
*
|
|
10
|
+
* Handles the page meta data such as title, description, plus generate
|
|
11
|
+
* breadcrumbs from the frontend router. The title, description and breadcrumbs
|
|
12
|
+
* are updated automatically when the route changes.
|
|
13
|
+
*
|
|
14
|
+
* Available States : breadcrumbs, title, description
|
|
15
|
+
*/
|
|
16
|
+
export const usePageMeta = defineStore('pageMeta', () => {
|
|
17
|
+
/**
|
|
18
|
+
* Globally provided properties
|
|
19
|
+
*/
|
|
20
|
+
const route = useRoute()
|
|
21
|
+
const { translate } = useTranslator()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* States
|
|
25
|
+
*
|
|
26
|
+
* - title: The current page title
|
|
27
|
+
* - description: The current page description
|
|
28
|
+
* - breadcrumbs: The current page breadcrumbs
|
|
29
|
+
* - hideBreadcrumbs: Ask the component to hide the breadcrumbs on the page
|
|
30
|
+
* - hideTitle: Ask the component to hide the title on the page
|
|
31
|
+
*/
|
|
32
|
+
const title = ref<string>('')
|
|
33
|
+
const description = ref<string>('')
|
|
34
|
+
const breadcrumbs = ref<Breadcrumb[]>([])
|
|
35
|
+
const hideBreadcrumbs = ref<boolean>(false)
|
|
36
|
+
const hideTitle = ref<boolean>(false)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Actions - Refresh the breadcrumbs, title and description
|
|
40
|
+
*/
|
|
41
|
+
function refresh() {
|
|
42
|
+
// Reset default visibility attributes
|
|
43
|
+
hideBreadcrumbs.value = false
|
|
44
|
+
hideTitle.value = false
|
|
45
|
+
|
|
46
|
+
// Get route trail
|
|
47
|
+
const matchedRoutes = route.matched
|
|
48
|
+
|
|
49
|
+
// Filter to remove routes without title and assign values as defined
|
|
50
|
+
// in the breadcrumbs interface.
|
|
51
|
+
const crumbs = matchedRoutes
|
|
52
|
+
.filter(
|
|
53
|
+
(routeItem) => routeItem.meta.title !== undefined && routeItem.meta.title !== ''
|
|
54
|
+
)
|
|
55
|
+
.map((routeItem) => {
|
|
56
|
+
return {
|
|
57
|
+
label: routeItem.meta?.title || '',
|
|
58
|
+
to: routeItem.path
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Add site title as first breadcrumb
|
|
63
|
+
crumbs.unshift({
|
|
64
|
+
label: siteTitle.value,
|
|
65
|
+
to: '/'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Replace ref with new values
|
|
69
|
+
breadcrumbs.value = crumbs
|
|
70
|
+
|
|
71
|
+
// Update Page Title & Description with current route
|
|
72
|
+
title.value = route.meta.title || ''
|
|
73
|
+
description.value = translate(route.meta.description || '')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update the document title
|
|
77
|
+
function updatePageTitle() {
|
|
78
|
+
document.title = pageFullTitle.value
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Update the document description in the HTML meta tag
|
|
82
|
+
function updatePageDescription() {
|
|
83
|
+
const descriptionElement = document.querySelector('head meta[name="description"]')
|
|
84
|
+
descriptionElement?.setAttribute('content', description.value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Computed Properties - Getters
|
|
89
|
+
*
|
|
90
|
+
* - siteTitle: Return the site title from the config Store
|
|
91
|
+
* - pageFullTitle: Return the full page title
|
|
92
|
+
*/
|
|
93
|
+
const siteTitle = computed<string>(() => useConfigStore().get('site.title') || '')
|
|
94
|
+
const pageFullTitle = computed<string>(() => {
|
|
95
|
+
return title.value ? translate(title.value) + ' | ' + siteTitle.value : siteTitle.value
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Watchers - route, page title and description changes
|
|
100
|
+
*/
|
|
101
|
+
watch(route, refresh, { immediate: true })
|
|
102
|
+
watch(pageFullTitle, updatePageTitle, { immediate: true })
|
|
103
|
+
watch(description, updatePageDescription, { immediate: true })
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns the states and actions
|
|
107
|
+
*/
|
|
108
|
+
return { breadcrumbs, title, description, hideBreadcrumbs, hideTitle }
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
interface Breadcrumb {
|
|
112
|
+
label: string
|
|
113
|
+
to: string
|
|
114
|
+
}
|
package/app/assets/index.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
import { useConfigStore, useTranslator } from './stores'
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Core Sprinkle initialization recipe.
|
|
6
|
+
*
|
|
7
|
+
* This recipe is responsible for loading the configuration from the api.
|
|
8
|
+
*/
|
|
3
9
|
export default {
|
|
4
|
-
install: () => {
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
install: (app: App) => {
|
|
11
|
+
useConfigStore().load()
|
|
12
|
+
|
|
13
|
+
// Load translations & add $t to global properties
|
|
14
|
+
const { translate, translateDate, load } = useTranslator()
|
|
15
|
+
load()
|
|
16
|
+
app.config.globalProperties.$t = translate
|
|
17
|
+
app.config.globalProperties.$tdate = translateDate
|
|
7
18
|
}
|
|
8
19
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Api Interface - What the API expects and what it returns
|
|
3
|
+
*
|
|
4
|
+
* This interface is tied to the `DictionaryController` API, accessed at the
|
|
5
|
+
* GET `/api/dictionary` endpoint.
|
|
6
|
+
*
|
|
7
|
+
* This api doesn't have a corresponding Request data interface.
|
|
8
|
+
*/
|
|
9
|
+
export interface DictionaryResponse {
|
|
10
|
+
identifier: string
|
|
11
|
+
config: DictionaryConfig
|
|
12
|
+
dictionary: DictionaryEntries
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DictionaryEntries {
|
|
16
|
+
[key: string]: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DictionaryConfig {
|
|
20
|
+
name: string
|
|
21
|
+
regional: string
|
|
22
|
+
authors: string[]
|
|
23
|
+
plural_rule: number
|
|
24
|
+
dates: string
|
|
25
|
+
}
|
|
@@ -1,4 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for custom routes meta fields.
|
|
3
|
+
*
|
|
4
|
+
* Meta Fields Added :
|
|
5
|
+
* - title: string - Page title
|
|
6
|
+
* - description: string - Page description
|
|
7
|
+
*
|
|
8
|
+
* Theses fields are used to set the document title and description, as well as
|
|
9
|
+
* for breadcrumbs generation.
|
|
10
|
+
*
|
|
11
|
+
* @see https://router.vuejs.org/guide/advanced/meta.html#TypeScript
|
|
12
|
+
*/
|
|
13
|
+
import 'vue-router'
|
|
14
|
+
|
|
15
|
+
declare module 'vue-router' {
|
|
16
|
+
interface RouteMeta {
|
|
17
|
+
title?: string
|
|
18
|
+
description?: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
1
22
|
export type { AlertInterface } from './alerts'
|
|
2
23
|
export type { AssociativeArray } from './common'
|
|
3
24
|
export { Severity } from './severity'
|
|
4
|
-
export type { Sprunjer } from './sprunjer'
|
|
25
|
+
export type { Sprunjer, SprunjerData, SprunjerListable, SprunjerListableOption } from './sprunjer'
|
|
26
|
+
export type { SprunjerRequest, SprunjerResponse } from './sprunjerApi'
|
|
27
|
+
export type { DictionaryResponse, DictionaryEntries, DictionaryConfig } from './DictionaryApi'
|
|
28
|
+
|
|
29
|
+
// Misc
|
|
30
|
+
export type { ApiResponse } from './ApiResponse'
|
|
@@ -7,12 +7,12 @@ import type { Ref, ComputedRef } from 'vue'
|
|
|
7
7
|
import type { AssociativeArray } from '.'
|
|
8
8
|
|
|
9
9
|
export interface Sprunjer {
|
|
10
|
-
dataUrl:
|
|
10
|
+
dataUrl: string | (() => string)
|
|
11
11
|
size: Ref<number>
|
|
12
12
|
page: Ref<number>
|
|
13
13
|
sorts: Ref<AssociativeArray>
|
|
14
14
|
filters: Ref<AssociativeArray>
|
|
15
|
-
data: Ref<
|
|
15
|
+
data: Ref<SprunjerData>
|
|
16
16
|
fetch: () => void
|
|
17
17
|
loading: Ref<boolean>
|
|
18
18
|
totalPages: ComputedRef<number>
|
|
@@ -24,3 +24,36 @@ export interface Sprunjer {
|
|
|
24
24
|
last: ComputedRef<number>
|
|
25
25
|
toggleSort: (column: string) => void
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sprunjer Data. Represents the data that is returned from any Sprunjer
|
|
30
|
+
* Composable. It is different than SprunjerResponse, as the response if what
|
|
31
|
+
* the API return, Data is what Vue provides. Both are similar, but Data doesn't
|
|
32
|
+
* have optional values.
|
|
33
|
+
*
|
|
34
|
+
* N.B.: "rows" uses a generic array. It can contain any object, and should
|
|
35
|
+
* actually be can be extended for each Sprunjer
|
|
36
|
+
*/
|
|
37
|
+
export interface SprunjerData {
|
|
38
|
+
count: number
|
|
39
|
+
count_filtered: number
|
|
40
|
+
rows: any[]
|
|
41
|
+
listable: SprunjerListable
|
|
42
|
+
sortable: string[]
|
|
43
|
+
filterable: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sprunjer Listable. Represents a listable for a Sprunjer.
|
|
48
|
+
*/
|
|
49
|
+
export interface SprunjerListable {
|
|
50
|
+
[key: string]: SprunjerListableOption[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sprunjer Listable Option. Represents a listable option for a Sprunjer.
|
|
55
|
+
*/
|
|
56
|
+
export interface SprunjerListableOption {
|
|
57
|
+
value: string
|
|
58
|
+
text: string
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AssociativeArray, SprunjerListable } from '.'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sprunje API Interfaces - What the API expects and what it returns.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sprunjer Response. All the data that is returned from any Sprunjer API.
|
|
9
|
+
* Note listable, sortable and filterable are optional when dealing with the API.
|
|
10
|
+
*
|
|
11
|
+
* N.B.: "rows" uses a generic array. It can contain any object, and should
|
|
12
|
+
* actually be can be extended for each Sprunjer
|
|
13
|
+
*/
|
|
14
|
+
export interface SprunjerResponse {
|
|
15
|
+
count: number
|
|
16
|
+
count_filtered: number
|
|
17
|
+
rows: any[]
|
|
18
|
+
listable?: SprunjerListable
|
|
19
|
+
sortable?: string[]
|
|
20
|
+
filterable?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sprunjer Request. All the parameters that can be passed to a Sprunjer.
|
|
25
|
+
* All parameters are optional.
|
|
26
|
+
*/
|
|
27
|
+
export interface SprunjerRequest {
|
|
28
|
+
size?: number
|
|
29
|
+
page?: number
|
|
30
|
+
sorts?: AssociativeArray
|
|
31
|
+
filters?: AssociativeArray
|
|
32
|
+
format?: string
|
|
33
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao
|
|
3
|
+
* 1 - everything: 0, 1, 2, ...
|
|
4
|
+
*/
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
6
|
+
export const rule0 = (number: number): number => {
|
|
7
|
+
return 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish),
|
|
12
|
+
* Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque),
|
|
13
|
+
* Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan)
|
|
14
|
+
* 1 - 1
|
|
15
|
+
* 2 - everything else: 0, 2, 3, ...
|
|
16
|
+
*/
|
|
17
|
+
export const rule1 = (number: number): number => {
|
|
18
|
+
return number === 1 ? 1 : 2
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Families: Romanic (French, Brazilian Portuguese)
|
|
23
|
+
* 1 - 0, 1
|
|
24
|
+
* 2 - everything else: 2, 3, ...
|
|
25
|
+
*/
|
|
26
|
+
export const rule2 = (number: number): number => {
|
|
27
|
+
return number === 0 || number === 1 ? 1 : 2
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Families: Baltic (Latvian)
|
|
32
|
+
* 1 - 0
|
|
33
|
+
* 2 - ends in 1, not 11: 1, 21, ... 101, 121, ...
|
|
34
|
+
* 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ...
|
|
35
|
+
*/
|
|
36
|
+
export const rule3 = (number: number): number => {
|
|
37
|
+
return number === 0 ? 1 : number % 10 == 1 && number % 100 != 11 ? 2 : 3
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Families: Celtic (Scottish Gaelic)
|
|
42
|
+
* 1 - is 1 or 11: 1, 11
|
|
43
|
+
* 2 - is 2 or 12: 2, 12
|
|
44
|
+
* 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19
|
|
45
|
+
* 4 - everything else: 0, 20, 21, ...
|
|
46
|
+
*/
|
|
47
|
+
export const rule4 = (number: number): number => {
|
|
48
|
+
return number === 1 || number == 11
|
|
49
|
+
? 1
|
|
50
|
+
: number === 2 || number === 12
|
|
51
|
+
? 2
|
|
52
|
+
: number >= 3 && number <= 19
|
|
53
|
+
? 3
|
|
54
|
+
: 4
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Families: Romanic (Romanian)
|
|
59
|
+
* 1 - 1
|
|
60
|
+
* 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ...
|
|
61
|
+
* 3 - everything else: 20, 21, ...
|
|
62
|
+
*/
|
|
63
|
+
export const rule5 = (number: number): number => {
|
|
64
|
+
return number === 1 ? 1 : number === 0 || (number % 100 > 0 && number % 100 < 20) ? 2 : 3
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Families: Baltic (Lithuanian)
|
|
69
|
+
* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
|
|
70
|
+
* 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ...
|
|
71
|
+
* 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ...
|
|
72
|
+
*/
|
|
73
|
+
export const rule6 = (number: number): number => {
|
|
74
|
+
return number % 10 === 1 && number % 100 !== 11
|
|
75
|
+
? 1
|
|
76
|
+
: number % 10 < 2 || (number % 100 >= 10 && number % 100 < 20)
|
|
77
|
+
? 2
|
|
78
|
+
: 3
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Families: Slavic (Croatian, Serbian, Russian, Ukrainian)
|
|
83
|
+
* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
|
|
84
|
+
* 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ...
|
|
85
|
+
* 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ...
|
|
86
|
+
*/
|
|
87
|
+
export const rule7 = (number: number): number => {
|
|
88
|
+
return number % 10 === 1 && number % 100 !== 11
|
|
89
|
+
? 1
|
|
90
|
+
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)
|
|
91
|
+
? 2
|
|
92
|
+
: 3
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Families: Slavic (Slovak, Czech)
|
|
97
|
+
* 1 - 1
|
|
98
|
+
* 2 - 2, 3, 4
|
|
99
|
+
* 3 - everything else: 0, 5, 6, 7, ...
|
|
100
|
+
*/
|
|
101
|
+
export const rule8 = (number: number): number => {
|
|
102
|
+
return number === 1 ? 1 : number >= 2 && number <= 4 ? 2 : 3
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Families: Slavic (Polish)
|
|
107
|
+
* 1 - 1
|
|
108
|
+
* 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ...
|
|
109
|
+
* 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ...
|
|
110
|
+
*/
|
|
111
|
+
export const rule9 = (number: number): number => {
|
|
112
|
+
return number === 1
|
|
113
|
+
? 1
|
|
114
|
+
: number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14)
|
|
115
|
+
? 2
|
|
116
|
+
: 3
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Families: Slavic (Slovenian, Sorbian)
|
|
121
|
+
* 1 - ends in 01: 1, 101, 201, ...
|
|
122
|
+
* 2 - ends in 02: 2, 102, 202, ...
|
|
123
|
+
* 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ...
|
|
124
|
+
* 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ...
|
|
125
|
+
*/
|
|
126
|
+
export const rule10 = (number: number): number => {
|
|
127
|
+
return number % 100 === 1
|
|
128
|
+
? 1
|
|
129
|
+
: number % 100 === 2
|
|
130
|
+
? 2
|
|
131
|
+
: number % 100 === 3 || number % 100 === 4
|
|
132
|
+
? 3
|
|
133
|
+
: 4
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Families: Celtic (Irish Gaeilge)
|
|
138
|
+
* 1 - 1
|
|
139
|
+
* 2 - 2
|
|
140
|
+
* 3 - is 3-6: 3, 4, 5, 6
|
|
141
|
+
* 4 - is 7-10: 7, 8, 9, 10
|
|
142
|
+
* 5 - everything else: 0, 11, 12, ...
|
|
143
|
+
*/
|
|
144
|
+
export const rule11 = (number: number): number => {
|
|
145
|
+
return number === 1
|
|
146
|
+
? 1
|
|
147
|
+
: number === 2
|
|
148
|
+
? 2
|
|
149
|
+
: number >= 3 && number <= 6
|
|
150
|
+
? 3
|
|
151
|
+
: number >= 7 && number <= 10
|
|
152
|
+
? 4
|
|
153
|
+
: 5
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Families: Semitic (Arabic).
|
|
158
|
+
*
|
|
159
|
+
* 1 - 1
|
|
160
|
+
* 2 - 2
|
|
161
|
+
* 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ...
|
|
162
|
+
* 4 - ends in 11-99: 11, ... 99, 111, 112, ...
|
|
163
|
+
* 5 - everything else: 100, 101, 102, 200, 201, 202, ...
|
|
164
|
+
* 6 - 0
|
|
165
|
+
*/
|
|
166
|
+
export const rule12 = (number: number): number => {
|
|
167
|
+
return number === 1
|
|
168
|
+
? 1
|
|
169
|
+
: number === 2
|
|
170
|
+
? 2
|
|
171
|
+
: number % 100 >= 3 && number % 100 <= 10
|
|
172
|
+
? 3
|
|
173
|
+
: number % 100 >= 11
|
|
174
|
+
? 4
|
|
175
|
+
: number != 0
|
|
176
|
+
? 5
|
|
177
|
+
: 6
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Families: Semitic (Maltese)
|
|
182
|
+
* 1 - 1
|
|
183
|
+
* 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ...
|
|
184
|
+
* 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ...
|
|
185
|
+
* 4 - everything else: 20, 21, ...
|
|
186
|
+
*/
|
|
187
|
+
export const rule13 = (number: number): number => {
|
|
188
|
+
return number === 1
|
|
189
|
+
? 1
|
|
190
|
+
: number === 0 || (number % 100 >= 1 && number % 100 < 11)
|
|
191
|
+
? 2
|
|
192
|
+
: number % 100 > 10 && number % 100 < 20
|
|
193
|
+
? 3
|
|
194
|
+
: 4
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Families: Slavic (Macedonian)
|
|
199
|
+
* 1 - ends in 1: 1, 11, 21, ...
|
|
200
|
+
* 2 - ends in 2: 2, 12, 22, ...
|
|
201
|
+
* 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ...
|
|
202
|
+
*/
|
|
203
|
+
export const rule14 = (number: number): number => {
|
|
204
|
+
return number % 10 === 1 ? 1 : number % 10 === 2 ? 2 : 3
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Families: Icelandic
|
|
209
|
+
* 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ...
|
|
210
|
+
* 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ...
|
|
211
|
+
*/
|
|
212
|
+
export const rule15 = (number: number): number => {
|
|
213
|
+
return number % 10 === 1 && number % 100 != 11 ? 1 : 2
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface PluralRules {
|
|
217
|
+
[key: number]: (pluralValue: number) => number
|
|
218
|
+
}
|