@things-factory/i18n-base 8.0.0-beta.1 → 8.0.0-beta.2
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 +4 -4
- package/client/bootstrap.ts +0 -16
- package/client/index.ts +0 -6
- package/client/page/terminology-page.ts +0 -408
- package/client/route.ts +0 -7
- package/server/apis/index.ts +0 -1
- package/server/apis/t.ts +0 -36
- package/server/controllers/clear-cache-for-translation.ts +0 -18
- package/server/index.ts +0 -6
- package/server/middlewares/index.ts +0 -36
- package/server/middlewares/koa-i18next/detect.ts +0 -108
- package/server/middlewares/koa-i18next/index.ts +0 -142
- package/server/middlewares/translations.ts +0 -27
- package/server/routers/translations-router.ts +0 -87
- package/server/routes.ts +0 -31
- package/server/service/index.ts +0 -27
- package/server/service/terminology/index.ts +0 -7
- package/server/service/terminology/terminology-mutation.ts +0 -171
- package/server/service/terminology/terminology-query.ts +0 -50
- package/server/service/terminology/terminology-type.ts +0 -54
- package/server/service/terminology/terminology.ts +0 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/i18n-base",
|
|
3
|
-
"version": "8.0.0-beta.
|
|
3
|
+
"version": "8.0.0-beta.2",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@operato/i18n": "^8.0.0-beta",
|
|
31
|
-
"@things-factory/auth-base": "^8.0.0-beta.
|
|
32
|
-
"@things-factory/shell": "^8.0.0-beta.
|
|
31
|
+
"@things-factory/auth-base": "^8.0.0-beta.2",
|
|
32
|
+
"@things-factory/shell": "^8.0.0-beta.2"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "f03431a09435511b2595515658f9cb8f78ba4ebb"
|
|
35
35
|
}
|
package/client/bootstrap.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { PageView } from '@operato/shell'
|
|
2
|
-
|
|
3
|
-
declare module '@operato/shell' {
|
|
4
|
-
interface PageView {
|
|
5
|
-
languageUpdated(i18next: any): void
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default function bootstrap() {
|
|
10
|
-
/*
|
|
11
|
-
* PageView의 lifecycle에 languageUpdate를 추가한다.
|
|
12
|
-
*/
|
|
13
|
-
PageView.prototype.languageUpdated = function (i18next) {
|
|
14
|
-
this.pageReset()
|
|
15
|
-
}
|
|
16
|
-
}
|
package/client/index.ts
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
import '@material/web/icon/icon.js'
|
|
2
|
-
|
|
3
|
-
import '@operato/data-grist'
|
|
4
|
-
import '@operato/context/ox-context-page-toolbar.js'
|
|
5
|
-
import '@operato/input/ox-input-select-buttons.js'
|
|
6
|
-
|
|
7
|
-
import { css, html } from 'lit'
|
|
8
|
-
import { customElement, query, state } from 'lit/decorators.js'
|
|
9
|
-
import { connect } from 'pwa-helpers/connect-mixin.js'
|
|
10
|
-
import gql from 'graphql-tag'
|
|
11
|
-
|
|
12
|
-
import { store, CustomAlert, PageView } from '@operato/shell'
|
|
13
|
-
import { client } from '@operato/graphql'
|
|
14
|
-
import { DataGrist, FetchOption, GristRecord } from '@operato/data-grist'
|
|
15
|
-
import { i18next, localize } from '@operato/i18n'
|
|
16
|
-
import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
|
|
17
|
-
import { isMobileDevice } from '@operato/utils'
|
|
18
|
-
import { getLanguages } from '@things-factory/auth-base/dist-client'
|
|
19
|
-
|
|
20
|
-
@customElement('terminology-page')
|
|
21
|
-
export class TerminologyPage extends connect(store)(localize(i18next)(PageView)) {
|
|
22
|
-
static styles = [
|
|
23
|
-
ScrollbarStyles,
|
|
24
|
-
CommonHeaderStyles,
|
|
25
|
-
css`
|
|
26
|
-
:host {
|
|
27
|
-
display: flex;
|
|
28
|
-
flex-direction: column;
|
|
29
|
-
|
|
30
|
-
overflow: hidden;
|
|
31
|
-
|
|
32
|
-
--grid-header-padding: 2px 0 2px 9px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
ox-grist {
|
|
36
|
-
overflow-y: auto;
|
|
37
|
-
flex: 1;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.header {
|
|
41
|
-
grid-template-areas: 'filters actions';
|
|
42
|
-
}
|
|
43
|
-
`
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
@state() private gristConfig: any
|
|
47
|
-
@state() private mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
|
|
48
|
-
@state() private state?: string[]
|
|
49
|
-
|
|
50
|
-
@query('ox-grist') private grist!: DataGrist
|
|
51
|
-
|
|
52
|
-
get context() {
|
|
53
|
-
return {
|
|
54
|
-
title: i18next.t('title.terminology'),
|
|
55
|
-
search: {
|
|
56
|
-
handler: (search: string) => {
|
|
57
|
-
this.grist.searchText = search
|
|
58
|
-
},
|
|
59
|
-
value: this.grist?.searchText || ''
|
|
60
|
-
},
|
|
61
|
-
filter: {
|
|
62
|
-
handler: () => {
|
|
63
|
-
this.grist.toggleHeadroom()
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
help: 'mfg-warehouse/term-page',
|
|
67
|
-
actions: [
|
|
68
|
-
{
|
|
69
|
-
title: i18next.t('button.clear-cache'),
|
|
70
|
-
action: this.clearCache.bind(this),
|
|
71
|
-
icon: 'ink_eraser'
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
title: i18next.t('button.save'),
|
|
75
|
-
action: this.save.bind(this),
|
|
76
|
-
icon: 'save'
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
title: i18next.t('button.delete'),
|
|
80
|
-
action: this.delete.bind(this),
|
|
81
|
-
icon: 'delete',
|
|
82
|
-
emphasis: {
|
|
83
|
-
danger: true
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
exportable: {
|
|
88
|
-
name: i18next.t('button.export'),
|
|
89
|
-
data: this.exportHandler.bind(this)
|
|
90
|
-
},
|
|
91
|
-
toolbar: false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async getFilterStatus() {
|
|
96
|
-
return (await getLanguages()).map(({ code, display }) => {
|
|
97
|
-
return { value: code, display }
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
render() {
|
|
102
|
-
const mode = this.mode || (isMobileDevice() ? 'LIST' : 'GRID')
|
|
103
|
-
|
|
104
|
-
return html`
|
|
105
|
-
<ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
|
|
106
|
-
<div slot="headroom" class="header">
|
|
107
|
-
<div class="filters">
|
|
108
|
-
<ox-filters-form autofocus without-search></ox-filters-form>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
112
|
-
</div>
|
|
113
|
-
</ox-grist>
|
|
114
|
-
`
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async pageInitialized(lifecycle) {
|
|
118
|
-
this.gristConfig = {
|
|
119
|
-
list: {
|
|
120
|
-
fields: ['name', 'description'],
|
|
121
|
-
details: ['active', 'updatedAt']
|
|
122
|
-
},
|
|
123
|
-
columns: [
|
|
124
|
-
{ type: 'gutter', gutterName: 'sequence' },
|
|
125
|
-
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
|
|
126
|
-
{
|
|
127
|
-
type: 'string',
|
|
128
|
-
name: 'id',
|
|
129
|
-
header: i18next.t('field.id'),
|
|
130
|
-
hidden: true,
|
|
131
|
-
width: 0
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
type: 'string',
|
|
135
|
-
name: 'name',
|
|
136
|
-
header: i18next.t('field.name'),
|
|
137
|
-
record: {
|
|
138
|
-
editable: true
|
|
139
|
-
},
|
|
140
|
-
filter: 'search',
|
|
141
|
-
sortable: true,
|
|
142
|
-
width: 250
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
type: 'select',
|
|
146
|
-
name: 'locale',
|
|
147
|
-
header: i18next.t('field.locale'),
|
|
148
|
-
record: {
|
|
149
|
-
editable: true,
|
|
150
|
-
options: [{ display: '', value: '' }, ...(await this.getFilterStatus())]
|
|
151
|
-
},
|
|
152
|
-
width: 90,
|
|
153
|
-
filter: {
|
|
154
|
-
type: 'select-buttons',
|
|
155
|
-
operator: 'in'
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
type: 'select',
|
|
160
|
-
name: 'category',
|
|
161
|
-
label: true,
|
|
162
|
-
header: i18next.t('field.category'),
|
|
163
|
-
record: {
|
|
164
|
-
editable: true,
|
|
165
|
-
options: ['', 'button', 'field', 'label', 'menu', 'text', 'title']
|
|
166
|
-
},
|
|
167
|
-
width: 90,
|
|
168
|
-
filter: 'in',
|
|
169
|
-
imex: true
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
type: 'string',
|
|
173
|
-
name: 'display',
|
|
174
|
-
label: true,
|
|
175
|
-
header: i18next.t('field.display'),
|
|
176
|
-
record: {
|
|
177
|
-
editable: true
|
|
178
|
-
},
|
|
179
|
-
filter: 'search',
|
|
180
|
-
sortable: true,
|
|
181
|
-
width: 380
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
type: 'resource-object',
|
|
185
|
-
name: 'updater',
|
|
186
|
-
header: i18next.t('field.updater'),
|
|
187
|
-
record: {
|
|
188
|
-
editable: false
|
|
189
|
-
},
|
|
190
|
-
sortable: true,
|
|
191
|
-
width: 120
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
type: 'datetime',
|
|
195
|
-
name: 'updatedAt',
|
|
196
|
-
header: i18next.t('field.updated_at'),
|
|
197
|
-
record: {
|
|
198
|
-
editable: false
|
|
199
|
-
},
|
|
200
|
-
sortable: true,
|
|
201
|
-
width: 180
|
|
202
|
-
}
|
|
203
|
-
],
|
|
204
|
-
rows: {
|
|
205
|
-
appendable: true,
|
|
206
|
-
selectable: {
|
|
207
|
-
multiple: true
|
|
208
|
-
}
|
|
209
|
-
/*handlers: {
|
|
210
|
-
click: (columns, data, column, record, rowIndex) => {
|
|
211
|
-
// TODO Implement the desired sub-view(like item-view) and use it in the popup content after importing
|
|
212
|
-
const popup = openPopup(html`
|
|
213
|
-
<item-view item-id=${record.id} style="background-color: white;">
|
|
214
|
-
</item-view>
|
|
215
|
-
`, {
|
|
216
|
-
backdrop: true,
|
|
217
|
-
size: 'large',
|
|
218
|
-
title: i18next.t('text.item view')
|
|
219
|
-
})
|
|
220
|
-
popup.onclosed = () => {
|
|
221
|
-
this.grist.fetch()
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}*/
|
|
225
|
-
},
|
|
226
|
-
sorters: [
|
|
227
|
-
{
|
|
228
|
-
name: 'name',
|
|
229
|
-
desc: true
|
|
230
|
-
}
|
|
231
|
-
]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async pageUpdated(changes: any, lifecycle: any) {
|
|
236
|
-
if (this.active) {
|
|
237
|
-
// do something here when this page just became as active
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
|
|
242
|
-
const response = await client.query({
|
|
243
|
-
query: gql`
|
|
244
|
-
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
245
|
-
terminologies(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
246
|
-
items {
|
|
247
|
-
id
|
|
248
|
-
name
|
|
249
|
-
locale
|
|
250
|
-
category
|
|
251
|
-
display
|
|
252
|
-
creator {
|
|
253
|
-
id
|
|
254
|
-
name
|
|
255
|
-
}
|
|
256
|
-
createdAt
|
|
257
|
-
updater {
|
|
258
|
-
id
|
|
259
|
-
name
|
|
260
|
-
}
|
|
261
|
-
updatedAt
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
total
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
`,
|
|
268
|
-
variables: {
|
|
269
|
-
filters,
|
|
270
|
-
pagination: { page, limit },
|
|
271
|
-
sortings
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
total: response.data.terminologies.total || 0,
|
|
277
|
-
records: response.data.terminologies.items || []
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
exportHandler() {
|
|
282
|
-
let records = [] as GristRecord
|
|
283
|
-
if (this.grist.selected && this.grist.selected.length > 0) {
|
|
284
|
-
records = this.grist.selected
|
|
285
|
-
} else {
|
|
286
|
-
records = this.grist.data.records
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
var headerSetting = this.grist.compiledConfig.columns
|
|
290
|
-
.filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
|
|
291
|
-
.map(column => {
|
|
292
|
-
return column.imex === true
|
|
293
|
-
? {
|
|
294
|
-
header: column.header.renderer(column),
|
|
295
|
-
key: column.name,
|
|
296
|
-
width: column.width,
|
|
297
|
-
type: column.type
|
|
298
|
-
}
|
|
299
|
-
: column.imex
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
var data = records.map(item => {
|
|
303
|
-
return {
|
|
304
|
-
id: item.id,
|
|
305
|
-
...this.gristConfig.columns
|
|
306
|
-
.filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
|
|
307
|
-
.reduce((record, column) => {
|
|
308
|
-
const key = column.imex === true ? column.name : column.imex.key
|
|
309
|
-
record[key] = key
|
|
310
|
-
.split('.')
|
|
311
|
-
.reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), item)
|
|
312
|
-
return record
|
|
313
|
-
}, {})
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
return { header: headerSetting, data: data }
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async delete() {
|
|
321
|
-
const selectedRecords = this.grist.selected
|
|
322
|
-
|
|
323
|
-
if (!selectedRecords || selectedRecords.length == 0) {
|
|
324
|
-
this.showToast(i18next.t('text.NOTHING_SELECTED'))
|
|
325
|
-
} else {
|
|
326
|
-
const answer = await CustomAlert({
|
|
327
|
-
type: 'question',
|
|
328
|
-
title: i18next.t('button.delete'),
|
|
329
|
-
text: i18next.t('text.are_you_sure'),
|
|
330
|
-
confirmButton: { text: i18next.t('button.delete') },
|
|
331
|
-
cancelButton: { text: i18next.t('button.cancel') }
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
if (!answer) {
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
let ids = selectedRecords.map(record => record.id)
|
|
339
|
-
|
|
340
|
-
const response = await client.mutate({
|
|
341
|
-
mutation: gql`
|
|
342
|
-
mutation ($ids: [String!]!) {
|
|
343
|
-
deleteTerminologies(ids: $ids)
|
|
344
|
-
}
|
|
345
|
-
`,
|
|
346
|
-
variables: {
|
|
347
|
-
ids
|
|
348
|
-
}
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
this.grist.fetch()
|
|
352
|
-
|
|
353
|
-
return response.data.deleteTerminologies
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async clearCache() {
|
|
358
|
-
const response = await client.mutate({
|
|
359
|
-
mutation: gql`
|
|
360
|
-
mutation {
|
|
361
|
-
clearTranslationsCache
|
|
362
|
-
}
|
|
363
|
-
`
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
if (!response.errors) {
|
|
367
|
-
this.showToast(i18next.t('text.info_x_successfully', { x: i18next.t('text.clearing-cache') }))
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async save() {
|
|
372
|
-
let changedRecords = this.grist.dirtyRecords
|
|
373
|
-
|
|
374
|
-
if (!changedRecords || changedRecords.length == 0) {
|
|
375
|
-
this.showToast(i18next.t('text.nothing_changed'))
|
|
376
|
-
} else {
|
|
377
|
-
const patches = changedRecords.map(patch => {
|
|
378
|
-
const patchField: any = patch.id ? { id: patch.id } : {}
|
|
379
|
-
const dirtyFields = patch.__dirtyfields__
|
|
380
|
-
for (let key in dirtyFields) {
|
|
381
|
-
patchField[key] = dirtyFields[key].after
|
|
382
|
-
}
|
|
383
|
-
patchField.cuFlag = patch.__dirty__
|
|
384
|
-
|
|
385
|
-
return patchField
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
const response = await client.mutate({
|
|
389
|
-
mutation: gql`
|
|
390
|
-
mutation ($patches: [TerminologyPatch!]!) {
|
|
391
|
-
updateMultipleTerminologies(patches: $patches) {
|
|
392
|
-
id
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
`,
|
|
396
|
-
variables: {
|
|
397
|
-
patches
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
this.grist.fetch()
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
showToast(message: string) {
|
|
406
|
-
document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))
|
|
407
|
-
}
|
|
408
|
-
}
|
package/client/route.ts
DELETED
package/server/apis/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './t'
|
package/server/apis/t.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
|
|
3
|
-
import { Terminology } from '../service/terminology/terminology'
|
|
4
|
-
|
|
5
|
-
export type T_Arugments = {
|
|
6
|
-
key: String
|
|
7
|
-
locale: String
|
|
8
|
-
options?: Object
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function t({ key, locale = 'en-US', options }: T_Arugments) {
|
|
12
|
-
let value = await getTerminology({
|
|
13
|
-
key,
|
|
14
|
-
locale
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
return value
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function getTerminology({ key, locale }) {
|
|
21
|
-
const repo = getRepository(Terminology)
|
|
22
|
-
var value
|
|
23
|
-
try {
|
|
24
|
-
let term = await repo.findOne({
|
|
25
|
-
where: {
|
|
26
|
-
name: key,
|
|
27
|
-
locale
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
value = term.display
|
|
31
|
-
} catch (e) {
|
|
32
|
-
value = key
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return value
|
|
36
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { config } from '@things-factory/env'
|
|
2
|
-
|
|
3
|
-
const disableCustomTerminologyFeature = config.get('i18n/disableCustomTerminologyFeature')
|
|
4
|
-
|
|
5
|
-
const path = require('path')
|
|
6
|
-
var fs = require('fs')
|
|
7
|
-
|
|
8
|
-
export function clearTranslationCacheForCertainDomain(subdomain: string) {
|
|
9
|
-
if (disableCustomTerminologyFeature) {
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const cacheFileFolder = path.join('cache', 'translations', subdomain)
|
|
14
|
-
|
|
15
|
-
if (fs.existsSync(cacheFileFolder)) {
|
|
16
|
-
fs.rmSync(cacheFileFolder, { recursive: true })
|
|
17
|
-
}
|
|
18
|
-
}
|
package/server/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import i18next from 'i18next'
|
|
3
|
-
|
|
4
|
-
import koaI18next from './koa-i18next'
|
|
5
|
-
import { build } from './translations'
|
|
6
|
-
|
|
7
|
-
i18next.init({
|
|
8
|
-
debug: false,
|
|
9
|
-
ns: ['translations'],
|
|
10
|
-
defaultNS: 'translations',
|
|
11
|
-
preload: ['ko', 'en', 'jp', 'zh', 'ms'],
|
|
12
|
-
fallbackLng: 'en',
|
|
13
|
-
interpolation: {
|
|
14
|
-
prefix: '{',
|
|
15
|
-
suffix: '}'
|
|
16
|
-
},
|
|
17
|
-
load: 'languageOnly',
|
|
18
|
-
keySeparator: false
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
build(fs)
|
|
22
|
-
|
|
23
|
-
process.on('client-rebuilt' as any, (app, fs) => {
|
|
24
|
-
/* rebuild once done webpacking assets. only for development mode. */
|
|
25
|
-
build(fs)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export function initMiddlewares(app: any) {
|
|
29
|
-
app.use(
|
|
30
|
-
koaI18next(i18next, {
|
|
31
|
-
lookupCookie: 'i18next',
|
|
32
|
-
order: ['cookie', 'header'],
|
|
33
|
-
next: true // if koa is version 2
|
|
34
|
-
})
|
|
35
|
-
)
|
|
36
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
const DEFAULT_ORDER = ['querystring', 'cookie', 'header']
|
|
2
|
-
|
|
3
|
-
const detectors = {
|
|
4
|
-
cookie: function (context, options) {
|
|
5
|
-
let cookie = options.lookupCookie || 'i18next'
|
|
6
|
-
return context.cookies.get(cookie)
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
// fork from i18next-express-middleware
|
|
10
|
-
header: function (context) {
|
|
11
|
-
let acceptLanguage = context.get('accept-language')
|
|
12
|
-
let found
|
|
13
|
-
let locales = []
|
|
14
|
-
if (acceptLanguage) {
|
|
15
|
-
let lngs = []
|
|
16
|
-
|
|
17
|
-
// associate language tags by their 'q' value (between 1 and 0)
|
|
18
|
-
acceptLanguage.split(',').forEach(function (l) {
|
|
19
|
-
let parts = l.split(';') // 'en-GB;q=0.8' -> ['en-GB', 'q=0.8']
|
|
20
|
-
|
|
21
|
-
// get the language tag qvalue: 'q=0.8' -> 0.8
|
|
22
|
-
let qvalue = 1 // default qvalue
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < parts.length; i++) {
|
|
25
|
-
let part = parts[i].split('=')
|
|
26
|
-
if (part[0] === 'q' && !isNaN(part[1])) {
|
|
27
|
-
qvalue = Number(part[1])
|
|
28
|
-
break
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// add the tag and primary subtag to the qvalue associations
|
|
32
|
-
lngs.push({
|
|
33
|
-
lng: parts[0],
|
|
34
|
-
q: qvalue
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
lngs.sort(function (a, b) {
|
|
39
|
-
return b.q - a.q
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < lngs.length; i++) {
|
|
43
|
-
locales.push(lngs[i].lng)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (locales.length) found = locales
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return found
|
|
50
|
-
},
|
|
51
|
-
path: function (context, options) {
|
|
52
|
-
let found
|
|
53
|
-
|
|
54
|
-
if (options.lookupPath !== undefined && context.params) {
|
|
55
|
-
found = context.params[options.lookupPath]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!found && options.lookupFromPathIndex !== undefined) {
|
|
59
|
-
let parts = context.path.split('/')
|
|
60
|
-
if (parts[0] === '') {
|
|
61
|
-
// Handle paths that start with a slash, i.e., '/foo' -> ['', 'foo']
|
|
62
|
-
parts.shift()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (parts.length > options.lookupFromPathIndex) {
|
|
66
|
-
found = parts[options.lookupFromPathIndex]
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return found
|
|
70
|
-
},
|
|
71
|
-
querystring: function (context, options) {
|
|
72
|
-
let name = options.lookupQuerystring || 'lng'
|
|
73
|
-
return context.query[name]
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
session: function (context, options) {
|
|
77
|
-
let name = options.lookupSession || 'lng'
|
|
78
|
-
return context.session && context.session[name]
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export default function (context, options = {}) {
|
|
83
|
-
let { order, fallback } = options as any
|
|
84
|
-
order = order && Array.isArray(order) ? order : DEFAULT_ORDER
|
|
85
|
-
|
|
86
|
-
let lngs = []
|
|
87
|
-
|
|
88
|
-
for (let i = 0, len = order.length; i < len; i++) {
|
|
89
|
-
let detector = detectors[order[i]]
|
|
90
|
-
let lng
|
|
91
|
-
if (detector) {
|
|
92
|
-
lng = detector(context, options)
|
|
93
|
-
}
|
|
94
|
-
if (lng && typeof lng === 'string') {
|
|
95
|
-
lngs.push(lng)
|
|
96
|
-
} else {
|
|
97
|
-
lngs = lngs.concat(lng)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
let found
|
|
101
|
-
for (let i = 0, len = lngs.length; i < len; i++) {
|
|
102
|
-
let cleanedLng = context.i18next.services.languageUtils.formatLanguageCode(lngs[i])
|
|
103
|
-
if (context.i18next.services.languageUtils.isSupportedCode(cleanedLng)) found = cleanedLng
|
|
104
|
-
if (found) break
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return found || fallback
|
|
108
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import detect from './detect'
|
|
2
|
-
|
|
3
|
-
import { config } from '@things-factory/env'
|
|
4
|
-
|
|
5
|
-
const disableUserFavoredLanguage = config.get('i18n/disableUserFavoredLanguage', false)
|
|
6
|
-
const defaultLanguage = config.get('i18n/defaultLanguage', 'en-US')
|
|
7
|
-
|
|
8
|
-
export default function koaI18next(i18next, options = {} as any) {
|
|
9
|
-
return async function i18nextMiddleware(ctx, next) {
|
|
10
|
-
ctx.i18next = i18next
|
|
11
|
-
|
|
12
|
-
let lng = disableUserFavoredLanguage ? defaultLanguage : detect(ctx, options)
|
|
13
|
-
lng && setLanguage(ctx, lng, options)
|
|
14
|
-
|
|
15
|
-
ctx.t = function (...args) {
|
|
16
|
-
// do detect path
|
|
17
|
-
if (!lng && isDetectPath(options.order)) {
|
|
18
|
-
lng = detect(ctx, Object.assign(options, { order: ['path'] }))
|
|
19
|
-
lng && setLanguage(ctx, lng, options)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (args.length === 1) {
|
|
23
|
-
args.push({})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
for (let i = 0, len = args.length; i < len; i++) {
|
|
27
|
-
let arg = args[i]
|
|
28
|
-
if (typeof arg === 'object' && !Array.isArray(arg)) {
|
|
29
|
-
arg.lng = lng
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return i18next.t.apply(i18next, args)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await next()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function setPath(object, path, newValue) {
|
|
40
|
-
let stack
|
|
41
|
-
if (typeof path !== 'string') stack = [].concat(path)
|
|
42
|
-
if (typeof path === 'string') stack = path.split('.')
|
|
43
|
-
|
|
44
|
-
while (stack.length > 1) {
|
|
45
|
-
let key = stack.shift()
|
|
46
|
-
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.')
|
|
47
|
-
if (!object[key]) object[key] = {}
|
|
48
|
-
object = object[key]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let key = stack.shift()
|
|
52
|
-
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.')
|
|
53
|
-
object[key] = newValue
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
koaI18next.getResourcesHandler = function (i18next, options) {
|
|
57
|
-
options = options || {}
|
|
58
|
-
let maxAge = options.maxAge || 60 * 60 * 24 * 30
|
|
59
|
-
const propertyParam = options.propertyParam || 'query'
|
|
60
|
-
|
|
61
|
-
return async function (ctx, next) {
|
|
62
|
-
if (options.path && ctx.path !== options.path) {
|
|
63
|
-
return await next()
|
|
64
|
-
}
|
|
65
|
-
if (!i18next.services.backendConnector) return ctx.throw(404, 'koa-i18next-middleware:: no backend configured')
|
|
66
|
-
|
|
67
|
-
let resources = {}
|
|
68
|
-
|
|
69
|
-
ctx.type = 'json'
|
|
70
|
-
if (options.cache !== undefined ? options.cache : process.env.NODE_ENV === 'production') {
|
|
71
|
-
ctx.set('Cache-Control', 'public, max-age=' + maxAge)
|
|
72
|
-
ctx.set('Expires', new Date(new Date().getTime() + maxAge * 1000).toUTCString())
|
|
73
|
-
} else {
|
|
74
|
-
ctx.set('Pragma', 'no-cache')
|
|
75
|
-
ctx.set('Cache-Control', 'no-cache')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let languages = ctx[propertyParam][options.lngParam || 'lng']
|
|
79
|
-
? ctx[propertyParam][options.lngParam || 'lng'].split(' ')
|
|
80
|
-
: []
|
|
81
|
-
let namespaces = ctx[propertyParam][options.nsParam || 'ns']
|
|
82
|
-
? ctx[propertyParam][options.nsParam || 'ns'].split(' ')
|
|
83
|
-
: []
|
|
84
|
-
|
|
85
|
-
// extend ns
|
|
86
|
-
namespaces.forEach(ns => {
|
|
87
|
-
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) i18next.options.ns.push(ns)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
i18next.services.backendConnector.load(languages, namespaces, function () {
|
|
91
|
-
languages.forEach(lng => {
|
|
92
|
-
namespaces.forEach(ns => {
|
|
93
|
-
setPath(resources, [lng, ns], i18next.getResourceBundle(lng, ns))
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
ctx.body = resources
|
|
97
|
-
})
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
koaI18next.getMissingKeyHandler = function (i18next, options) {
|
|
102
|
-
options = options || {}
|
|
103
|
-
const propertyParam = options.propertyParam || 'query'
|
|
104
|
-
|
|
105
|
-
return async function (ctx, next) {
|
|
106
|
-
if (options.path && ctx.path !== options.path) {
|
|
107
|
-
return await next()
|
|
108
|
-
}
|
|
109
|
-
let lng = ctx[propertyParam][options.lngParam || 'lng']
|
|
110
|
-
let ns = ctx[propertyParam][options.nsParam || 'ns']
|
|
111
|
-
|
|
112
|
-
if (!i18next.services.backendConnector) return ctx.throw(404, 'koa-i18next-middleware:: no backend configured')
|
|
113
|
-
|
|
114
|
-
for (var m in ctx.request.body) {
|
|
115
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, ctx.request.body[m])
|
|
116
|
-
}
|
|
117
|
-
ctx.body = 'ok'
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function isDetectPath(order = []) {
|
|
122
|
-
return order.indexOf('path') !== -1
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function setLanguage(context, lng, options = {} as any) {
|
|
126
|
-
const { lookupCookie, lookupPath, lookupSession } = options
|
|
127
|
-
context.locals = Object.assign(context.locals || {}, { lng })
|
|
128
|
-
context.state = Object.assign(context.state || {}, { lng })
|
|
129
|
-
context.language = context.lng = lng
|
|
130
|
-
context.set('content-language', lng)
|
|
131
|
-
|
|
132
|
-
if (lookupCookie) {
|
|
133
|
-
context.cookies.set(lookupCookie, lng, {
|
|
134
|
-
httpOnly: false,
|
|
135
|
-
signed: false,
|
|
136
|
-
sameSite: 'strict'
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
if (lookupSession && context.session) {
|
|
140
|
-
context.session[lookupSession] = lng
|
|
141
|
-
}
|
|
142
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import FS from 'fs'
|
|
2
|
-
import i18next from 'i18next'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
|
|
5
|
-
const { appRootPath } = require('@things-factory/env')
|
|
6
|
-
|
|
7
|
-
export function build(fs) {
|
|
8
|
-
fs = fs || FS
|
|
9
|
-
|
|
10
|
-
const PUBLIC_ROOT = path.resolve(appRootPath, 'dist-app') // webpack config output.path
|
|
11
|
-
|
|
12
|
-
const ns = 'translations'
|
|
13
|
-
var folder = path.join(PUBLIC_ROOT, 'translations')
|
|
14
|
-
try {
|
|
15
|
-
fs.readdirSync(folder).forEach(file => {
|
|
16
|
-
try {
|
|
17
|
-
var lng = file.split('.')[0]
|
|
18
|
-
var resourcePath = path.join(folder, file)
|
|
19
|
-
var resources = JSON.parse(fs.readFileSync(resourcePath))
|
|
20
|
-
i18next.removeResourceBundle(lng, ns)
|
|
21
|
-
i18next.addResourceBundle(lng, ns, resources, true, true)
|
|
22
|
-
} catch (e) {
|
|
23
|
-
console.error(e)
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
} catch (e) {}
|
|
27
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import Router from 'koa-router'
|
|
2
|
-
import { ILike } from 'typeorm'
|
|
3
|
-
|
|
4
|
-
import { getRepository } from '@things-factory/shell'
|
|
5
|
-
import { Terminology } from '../service/terminology/terminology'
|
|
6
|
-
import { config } from '@things-factory/env'
|
|
7
|
-
|
|
8
|
-
const path = require('path')
|
|
9
|
-
const cacheFS = require('fs')
|
|
10
|
-
var fs = require('fs')
|
|
11
|
-
|
|
12
|
-
const disableCustomTerminologyFeature = config.get('i18n/disableCustomTerminologyFeature')
|
|
13
|
-
|
|
14
|
-
process.on('client-rebuilt' as any, (app, compilerFileSystem) => {
|
|
15
|
-
/* change filesystem whenever webpacking assets done. only for development mode. */
|
|
16
|
-
fs = compilerFileSystem
|
|
17
|
-
|
|
18
|
-
if (!disableCustomTerminologyFeature) {
|
|
19
|
-
const cacheFileFolder = path.join('cache', 'translations')
|
|
20
|
-
|
|
21
|
-
if (cacheFS.existsSync(cacheFileFolder)) {
|
|
22
|
-
cacheFS.rmSync(cacheFileFolder, { recursive: true })
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
export const translationsRouter = new Router()
|
|
28
|
-
|
|
29
|
-
translationsRouter.get('/translations/:filename', async (context, next) => {
|
|
30
|
-
const { filename } = context.params
|
|
31
|
-
const { domain } = context.state
|
|
32
|
-
|
|
33
|
-
const locale = (filename || 'en.json').split('.')[0]
|
|
34
|
-
|
|
35
|
-
// context.set('Cache-Control', 'public, max-age=31536000') // 캐시 기간 설정 : 1년
|
|
36
|
-
// context.set('Expires', new Date(Date.now() + 31536000000).toUTCString()) // 캐시 만료일 설정
|
|
37
|
-
|
|
38
|
-
if (disableCustomTerminologyFeature) {
|
|
39
|
-
var file = fs.readFileSync(path.resolve(path.join('dist-app', 'translations', filename)))
|
|
40
|
-
|
|
41
|
-
context.type = 'application/json'
|
|
42
|
-
context.body = file
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const cacheFileFolder = path.join('cache', 'translations', domain.subdomain)
|
|
47
|
-
const cacheFilePath = path.join(cacheFileFolder, filename)
|
|
48
|
-
|
|
49
|
-
if (cacheFS.existsSync(cacheFilePath)) {
|
|
50
|
-
var file = cacheFS.readFileSync(cacheFilePath)
|
|
51
|
-
} else {
|
|
52
|
-
var file = fs.readFileSync(path.resolve(path.join('dist-app', 'translations', filename)))
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const terminologies = await getRepository(Terminology).find({
|
|
56
|
-
where: { domain: { id: domain.id }, locale: ILike(locale + '%') }
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const json = JSON.parse(file)
|
|
60
|
-
|
|
61
|
-
terminologies.forEach(term => {
|
|
62
|
-
const { name, category, display } = term
|
|
63
|
-
json[`${category}.${name}`] = display
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
if (!cacheFS.existsSync(cacheFileFolder)) {
|
|
68
|
-
cacheFS.mkdirSync(cacheFileFolder, { recursive: true })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
cacheFS.writeFileSync(cacheFilePath, JSON.stringify(json))
|
|
72
|
-
} catch (err) {
|
|
73
|
-
console.error(err)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
context.type = 'application/json'
|
|
77
|
-
context.body = json
|
|
78
|
-
|
|
79
|
-
return
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.error(err)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
context.type = 'application/json'
|
|
86
|
-
context.body = file
|
|
87
|
-
})
|
package/server/routes.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import i18next from 'i18next'
|
|
2
|
-
import { config } from '@things-factory/env'
|
|
3
|
-
|
|
4
|
-
const disableCustomTerminologyFeature = config.get('i18n/disableCustomTerminologyFeature')
|
|
5
|
-
const isPathBaseDomain = !config.get('subdomain') && !config.get('useVirtualHostBasedDomain')
|
|
6
|
-
|
|
7
|
-
import { translationsRouter } from './routers/translations-router'
|
|
8
|
-
|
|
9
|
-
process.on('bootstrap-module-global-public-route' as any, (app, globalPublicRouter) => {
|
|
10
|
-
globalPublicRouter.get('/translations/translate/:lng/:key', async (context, next) => {
|
|
11
|
-
const { lng, key } = context.params
|
|
12
|
-
const translated = i18next.getFixedT(lng, 'translations' /* ns */)(key)
|
|
13
|
-
|
|
14
|
-
context.body = translated
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
globalPublicRouter.get('/translations/translate/:key', async (context, next) => {
|
|
18
|
-
const { key } = context.params
|
|
19
|
-
const translated = context.t(key)
|
|
20
|
-
|
|
21
|
-
context.body = translated
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
process.on('bootstrap-module-domain-public-route' as any, (app, domainPublicRouter) => {
|
|
26
|
-
if (isPathBaseDomain) {
|
|
27
|
-
domainPublicRouter.use('/domain/:subdomain', translationsRouter.routes(), translationsRouter.allowedMethods())
|
|
28
|
-
} else if (!disableCustomTerminologyFeature) {
|
|
29
|
-
domainPublicRouter.use('', translationsRouter.routes(), translationsRouter.allowedMethods())
|
|
30
|
-
}
|
|
31
|
-
})
|
package/server/service/index.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/* EXPORT ENTITY TYPES */
|
|
2
|
-
export * from './terminology/terminology'
|
|
3
|
-
export * from './terminology/terminology-type'
|
|
4
|
-
|
|
5
|
-
/* IMPORT ENTITIES AND RESOLVERS */
|
|
6
|
-
import {
|
|
7
|
-
entities as TerminologyEntities,
|
|
8
|
-
resolvers as TerminologyResolvers,
|
|
9
|
-
subscribers as TerminologySubscribers
|
|
10
|
-
} from './terminology'
|
|
11
|
-
|
|
12
|
-
export const entities = [
|
|
13
|
-
/* ENTITIES */
|
|
14
|
-
...TerminologyEntities
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
export const subscribers = [
|
|
18
|
-
/* SUBSCRIBERS */
|
|
19
|
-
...TerminologySubscribers
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
export const schema = {
|
|
23
|
-
resolverClasses: [
|
|
24
|
-
/* RESOLVER CLASSES */
|
|
25
|
-
...TerminologyResolvers
|
|
26
|
-
]
|
|
27
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Terminology } from './terminology'
|
|
2
|
-
import { TerminologyQuery } from './terminology-query'
|
|
3
|
-
import { TerminologyMutation } from './terminology-mutation'
|
|
4
|
-
|
|
5
|
-
export const entities = [Terminology]
|
|
6
|
-
export const resolvers = [TerminologyQuery, TerminologyMutation]
|
|
7
|
-
export const subscribers = []
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
|
|
2
|
-
import { In } from 'typeorm'
|
|
3
|
-
|
|
4
|
-
import { Terminology } from './terminology'
|
|
5
|
-
import { NewTerminology, TerminologyPatch } from './terminology-type'
|
|
6
|
-
|
|
7
|
-
import { clearTranslationCacheForCertainDomain } from '../../controllers/clear-cache-for-translation'
|
|
8
|
-
|
|
9
|
-
function clearCache(subdomain: string) {
|
|
10
|
-
/** multiple update/delete시 모든 엔티티에 대해서 subscription 이 걸리므로, EventSubscriber를 사용하지 않음. */
|
|
11
|
-
try {
|
|
12
|
-
clearTranslationCacheForCertainDomain(subdomain)
|
|
13
|
-
} catch (err) {
|
|
14
|
-
console.error('Failed to delete the translations cache directory:', err)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
@Resolver(Terminology)
|
|
19
|
-
export class TerminologyMutation {
|
|
20
|
-
@Directive('@transaction')
|
|
21
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
22
|
-
@Mutation(returns => Terminology, { description: 'To create new Terminology' })
|
|
23
|
-
async createTerminology(@Arg('terminology') terminology: NewTerminology, @Ctx() context: any): Promise<Terminology> {
|
|
24
|
-
const { domain, user, tx } = context.state
|
|
25
|
-
|
|
26
|
-
const result = await tx.getRepository(Terminology).save({
|
|
27
|
-
...terminology,
|
|
28
|
-
domain,
|
|
29
|
-
creator: user,
|
|
30
|
-
updater: user
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
clearCache(domain.subdomain)
|
|
34
|
-
|
|
35
|
-
return result
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
@Directive('@transaction')
|
|
39
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
40
|
-
@Mutation(returns => Terminology, { description: 'To modify Terminology information' })
|
|
41
|
-
async updateTerminology(
|
|
42
|
-
@Arg('id') id: string,
|
|
43
|
-
@Arg('patch') patch: TerminologyPatch,
|
|
44
|
-
@Ctx() context: any
|
|
45
|
-
): Promise<Terminology> {
|
|
46
|
-
const { domain, user, tx } = context.state
|
|
47
|
-
|
|
48
|
-
const repository = tx.getRepository(Terminology)
|
|
49
|
-
const terminology = await repository.findOne({
|
|
50
|
-
where: { domain: { id: domain.id }, id }
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const result = await repository.save({
|
|
54
|
-
...terminology,
|
|
55
|
-
...patch,
|
|
56
|
-
updater: user
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
clearCache(domain.subdomain)
|
|
60
|
-
|
|
61
|
-
return result
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@Directive('@transaction')
|
|
65
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
66
|
-
@Mutation(returns => [Terminology], { description: "To modify multiple Terminologies' information" })
|
|
67
|
-
async updateMultipleTerminologies(
|
|
68
|
-
@Arg('patches', type => [TerminologyPatch]) patches: TerminologyPatch[],
|
|
69
|
-
@Ctx() context: any
|
|
70
|
-
): Promise<Terminology[]> {
|
|
71
|
-
const { domain, user, tx } = context.state
|
|
72
|
-
|
|
73
|
-
let results = []
|
|
74
|
-
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
|
75
|
-
const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
|
|
76
|
-
const terminologyRepo = tx.getRepository(Terminology)
|
|
77
|
-
|
|
78
|
-
if (_createRecords.length > 0) {
|
|
79
|
-
for (let i = 0; i < _createRecords.length; i++) {
|
|
80
|
-
const newRecord = _createRecords[i]
|
|
81
|
-
|
|
82
|
-
const result = await terminologyRepo.save({
|
|
83
|
-
...newRecord,
|
|
84
|
-
domain,
|
|
85
|
-
creator: user,
|
|
86
|
-
updater: user
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
results.push({ ...result, cuFlag: '+' })
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (_updateRecords.length > 0) {
|
|
94
|
-
for (let i = 0; i < _updateRecords.length; i++) {
|
|
95
|
-
const newRecord = _updateRecords[i]
|
|
96
|
-
const terminology = await terminologyRepo.findOneBy({ id: newRecord.id })
|
|
97
|
-
|
|
98
|
-
const result = await terminologyRepo.save({
|
|
99
|
-
...terminology,
|
|
100
|
-
...newRecord,
|
|
101
|
-
updater: user
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
results.push({ ...result, cuFlag: 'M' })
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
clearCache(domain.subdomain)
|
|
109
|
-
|
|
110
|
-
return results
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@Directive('@transaction')
|
|
114
|
-
@Mutation(returns => Boolean, { description: 'To clear translations cache' })
|
|
115
|
-
async clearTranslationsCache(@Ctx() context: any): Promise<boolean> {
|
|
116
|
-
const { domain, tx } = context.state
|
|
117
|
-
|
|
118
|
-
clearCache(domain.subdomain)
|
|
119
|
-
|
|
120
|
-
return true
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
@Directive('@transaction')
|
|
124
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
125
|
-
@Mutation(returns => Boolean, { description: 'To delete Terminology' })
|
|
126
|
-
async deleteTerminology(@Arg('id') id: string, @Ctx() context: any): Promise<boolean> {
|
|
127
|
-
const { domain, tx } = context.state
|
|
128
|
-
|
|
129
|
-
await tx.getRepository(Terminology).delete({ domain: { id: domain.id }, id })
|
|
130
|
-
|
|
131
|
-
clearCache(domain.subdomain)
|
|
132
|
-
|
|
133
|
-
return true
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@Directive('@transaction')
|
|
137
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
138
|
-
@Mutation(returns => Boolean, { description: 'To delete multiple Terminologies' })
|
|
139
|
-
async deleteTerminologies(@Arg('ids', type => [String]) ids: string[], @Ctx() context: any): Promise<boolean> {
|
|
140
|
-
const { domain, tx } = context.state
|
|
141
|
-
|
|
142
|
-
await tx.getRepository(Terminology).delete({
|
|
143
|
-
domain: { id: domain.id },
|
|
144
|
-
id: In(ids)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
clearCache(domain.subdomain)
|
|
148
|
-
|
|
149
|
-
return true
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@Directive('@transaction')
|
|
153
|
-
@Directive('@privilege(category: "terminology", privilege: "mutation", domainOwnerGranted: true)')
|
|
154
|
-
@Mutation(returns => Boolean, { description: 'To import multiple Terminologies' })
|
|
155
|
-
async importTerminologies(
|
|
156
|
-
@Arg('terminologies', type => [TerminologyPatch]) terminologies: TerminologyPatch[],
|
|
157
|
-
@Ctx() context: any
|
|
158
|
-
): Promise<boolean> {
|
|
159
|
-
const { domain, tx } = context.state
|
|
160
|
-
|
|
161
|
-
await Promise.all(
|
|
162
|
-
terminologies.map(async (terminology: TerminologyPatch) => {
|
|
163
|
-
const createdTerminology: Terminology = await tx.getRepository(Terminology).save({ domain, ...terminology })
|
|
164
|
-
})
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
clearCache(domain.subdomain)
|
|
168
|
-
|
|
169
|
-
return true
|
|
170
|
-
}
|
|
171
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Arg, Args, Ctx, FieldResolver, Query, Resolver, Root } from 'type-graphql'
|
|
2
|
-
|
|
3
|
-
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
4
|
-
import { User } from '@things-factory/auth-base'
|
|
5
|
-
|
|
6
|
-
import { Terminology } from './terminology'
|
|
7
|
-
import { TerminologyList } from './terminology-type'
|
|
8
|
-
|
|
9
|
-
@Resolver(Terminology)
|
|
10
|
-
export class TerminologyQuery {
|
|
11
|
-
@Query(returns => Terminology, { description: 'To fetch a Terminology' })
|
|
12
|
-
async terminology(@Arg('id') id: string, @Ctx() context: any): Promise<Terminology> {
|
|
13
|
-
const { domain } = context.state
|
|
14
|
-
|
|
15
|
-
return await getRepository(Terminology).findOne({
|
|
16
|
-
where: { domain: { id: domain.id }, id }
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@Query(returns => TerminologyList, { description: 'To fetch multiple Terminologies' })
|
|
21
|
-
async terminologies(@Args(type => ListParam) params: ListParam, @Ctx() context: any): Promise<TerminologyList> {
|
|
22
|
-
const { domain } = context.state
|
|
23
|
-
|
|
24
|
-
const queryBuilder = getQueryBuilderFromListParams({
|
|
25
|
-
domain,
|
|
26
|
-
params,
|
|
27
|
-
repository: await getRepository(Terminology),
|
|
28
|
-
searchables: ['name', 'category', 'display']
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const [items, total] = await queryBuilder.getManyAndCount()
|
|
32
|
-
|
|
33
|
-
return { items, total }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@FieldResolver(type => Domain)
|
|
37
|
-
async domain(@Root() terminology: Terminology): Promise<Domain> {
|
|
38
|
-
return await getRepository(Domain).findOneBy({ id: terminology.domainId })
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@FieldResolver(type => User)
|
|
42
|
-
async updater(@Root() terminology: Terminology): Promise<User> {
|
|
43
|
-
return await getRepository(User).findOneBy({ id: terminology.updaterId })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@FieldResolver(type => User)
|
|
47
|
-
async creator(@Root() terminology: Terminology): Promise<User> {
|
|
48
|
-
return await getRepository(User).findOneBy({ id: terminology.creatorId })
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Field, ID, InputType, Int, ObjectType } from 'type-graphql'
|
|
2
|
-
|
|
3
|
-
import { Terminology } from './terminology'
|
|
4
|
-
|
|
5
|
-
@InputType()
|
|
6
|
-
export class NewTerminology {
|
|
7
|
-
@Field()
|
|
8
|
-
name: string
|
|
9
|
-
|
|
10
|
-
@Field({ nullable: true })
|
|
11
|
-
description?: string
|
|
12
|
-
|
|
13
|
-
@Field()
|
|
14
|
-
locale: string
|
|
15
|
-
|
|
16
|
-
@Field()
|
|
17
|
-
category: string
|
|
18
|
-
|
|
19
|
-
@Field()
|
|
20
|
-
display: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@InputType()
|
|
24
|
-
export class TerminologyPatch {
|
|
25
|
-
@Field(type => ID, { nullable: true })
|
|
26
|
-
id?: string
|
|
27
|
-
|
|
28
|
-
@Field({ nullable: true })
|
|
29
|
-
name?: string
|
|
30
|
-
|
|
31
|
-
@Field({ nullable: true })
|
|
32
|
-
description?: string
|
|
33
|
-
|
|
34
|
-
@Field({ nullable: true })
|
|
35
|
-
locale?: string
|
|
36
|
-
|
|
37
|
-
@Field({ nullable: true })
|
|
38
|
-
category?: string
|
|
39
|
-
|
|
40
|
-
@Field({ nullable: true })
|
|
41
|
-
display?: string
|
|
42
|
-
|
|
43
|
-
@Field({ nullable: true })
|
|
44
|
-
cuFlag?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@ObjectType()
|
|
48
|
-
export class TerminologyList {
|
|
49
|
-
@Field(type => [Terminology])
|
|
50
|
-
items: Terminology[]
|
|
51
|
-
|
|
52
|
-
@Field(type => Int)
|
|
53
|
-
total: number
|
|
54
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { Field, ID, ObjectType } from 'type-graphql'
|
|
2
|
-
import {
|
|
3
|
-
Column,
|
|
4
|
-
CreateDateColumn,
|
|
5
|
-
Entity,
|
|
6
|
-
Index,
|
|
7
|
-
ManyToOne,
|
|
8
|
-
PrimaryGeneratedColumn,
|
|
9
|
-
RelationId,
|
|
10
|
-
UpdateDateColumn
|
|
11
|
-
} from 'typeorm'
|
|
12
|
-
|
|
13
|
-
import { Domain } from '@things-factory/shell'
|
|
14
|
-
import { User } from '@things-factory/auth-base'
|
|
15
|
-
|
|
16
|
-
@Entity()
|
|
17
|
-
@ObjectType({ description: 'Entity for Terminology' })
|
|
18
|
-
@Index('ix_term_0', (term: Terminology) => [term.domain, term.locale, term.category, term.name], { unique: true })
|
|
19
|
-
@Index('ix_term_1', (term: Terminology) => [term.domain])
|
|
20
|
-
@Index('ix_term_2', (term: Terminology) => [term.domain, term.locale])
|
|
21
|
-
@Index('ix_term_3', (term: Terminology) => [term.domain, term.locale, term.name])
|
|
22
|
-
export class Terminology {
|
|
23
|
-
@PrimaryGeneratedColumn('uuid')
|
|
24
|
-
@Field(type => ID)
|
|
25
|
-
readonly id: string
|
|
26
|
-
|
|
27
|
-
@ManyToOne(type => Domain)
|
|
28
|
-
@Field(type => Domain)
|
|
29
|
-
domain?: Domain
|
|
30
|
-
|
|
31
|
-
@RelationId((terminology: Terminology) => terminology.domain)
|
|
32
|
-
domainId?: string
|
|
33
|
-
|
|
34
|
-
@Column()
|
|
35
|
-
@Field()
|
|
36
|
-
name?: string
|
|
37
|
-
|
|
38
|
-
@Column({ nullable: true })
|
|
39
|
-
@Field({ nullable: true })
|
|
40
|
-
description?: string
|
|
41
|
-
|
|
42
|
-
@Field()
|
|
43
|
-
@Column({ length: 20 })
|
|
44
|
-
locale?: string
|
|
45
|
-
|
|
46
|
-
@Field()
|
|
47
|
-
@Column({ length: 20 })
|
|
48
|
-
category?: string
|
|
49
|
-
|
|
50
|
-
@Field()
|
|
51
|
-
@Column()
|
|
52
|
-
display?: string
|
|
53
|
-
|
|
54
|
-
@CreateDateColumn()
|
|
55
|
-
@Field({ nullable: true })
|
|
56
|
-
createdAt?: Date
|
|
57
|
-
|
|
58
|
-
@UpdateDateColumn()
|
|
59
|
-
@Field({ nullable: true })
|
|
60
|
-
updatedAt?: Date
|
|
61
|
-
|
|
62
|
-
@ManyToOne(type => User, { nullable: true })
|
|
63
|
-
@Field(type => User, { nullable: true })
|
|
64
|
-
creator?: User
|
|
65
|
-
|
|
66
|
-
@RelationId((terminology: Terminology) => terminology.creator)
|
|
67
|
-
creatorId?: string
|
|
68
|
-
|
|
69
|
-
@ManyToOne(type => User, { nullable: true })
|
|
70
|
-
@Field(type => User, { nullable: true })
|
|
71
|
-
updater?: User
|
|
72
|
-
|
|
73
|
-
@RelationId((terminology: Terminology) => terminology.updater)
|
|
74
|
-
updaterId?: string
|
|
75
|
-
}
|