@things-factory/i18n-base 10.1.79 → 10.1.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/ai-entry/entry-target-shape.d.ts +82 -0
- package/dist-server/ai-entry/entry-target-shape.js +3 -0
- package/dist-server/ai-entry/entry-target-shape.js.map +1 -0
- package/dist-server/ai-entry/terminology-entry-rules.d.ts +36 -0
- package/dist-server/ai-entry/terminology-entry-rules.js +65 -0
- package/dist-server/ai-entry/terminology-entry-rules.js.map +1 -0
- package/dist-server/ai-entry/terminology-entry-targets.d.ts +11 -0
- package/dist-server/ai-entry/terminology-entry-targets.js +139 -0
- package/dist-server/ai-entry/terminology-entry-targets.js.map +1 -0
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/tests/terminology-entry-targets.test.ts +135 -0
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference';
|
|
2
|
+
export interface EntryReference {
|
|
3
|
+
query: string;
|
|
4
|
+
variables(text: string): Record<string, unknown>;
|
|
5
|
+
recordsOf(data: any, text: string): {
|
|
6
|
+
id: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export interface EntryField {
|
|
11
|
+
name: string;
|
|
12
|
+
type: EntryFieldType;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
values?: string[];
|
|
15
|
+
description?: string;
|
|
16
|
+
labelKey?: string;
|
|
17
|
+
total?: boolean;
|
|
18
|
+
valueKeyPrefix?: string;
|
|
19
|
+
reference?: EntryReference;
|
|
20
|
+
}
|
|
21
|
+
export interface EntryIssue {
|
|
22
|
+
field?: string;
|
|
23
|
+
code: string;
|
|
24
|
+
messageKey?: string;
|
|
25
|
+
params?: Record<string, unknown>;
|
|
26
|
+
blocking: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface EntryRow {
|
|
29
|
+
index: number;
|
|
30
|
+
values: Record<string, unknown>;
|
|
31
|
+
sources: unknown[];
|
|
32
|
+
issues: EntryIssue[];
|
|
33
|
+
}
|
|
34
|
+
export type EntryRecord = {
|
|
35
|
+
id: string;
|
|
36
|
+
} & Record<string, unknown>;
|
|
37
|
+
export interface EntryUpdate {
|
|
38
|
+
read: {
|
|
39
|
+
query: string;
|
|
40
|
+
variables(ids: string[]): Record<string, unknown>;
|
|
41
|
+
recordsOf(data: any, ids: string[]): EntryRecord[];
|
|
42
|
+
};
|
|
43
|
+
find?: {
|
|
44
|
+
query: string;
|
|
45
|
+
variables(criteria: Record<string, unknown>): Record<string, unknown>;
|
|
46
|
+
recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[];
|
|
47
|
+
criteria: EntryField[];
|
|
48
|
+
};
|
|
49
|
+
mutation: string;
|
|
50
|
+
variables(change: {
|
|
51
|
+
id: string;
|
|
52
|
+
record: EntryRecord;
|
|
53
|
+
values: Record<string, unknown>;
|
|
54
|
+
}): Record<string, unknown>;
|
|
55
|
+
labelOf?(record: EntryRecord): string;
|
|
56
|
+
fields?: string[];
|
|
57
|
+
/** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */
|
|
58
|
+
check?(rows: {
|
|
59
|
+
id: string;
|
|
60
|
+
record?: EntryRecord;
|
|
61
|
+
values: Record<string, unknown>;
|
|
62
|
+
}[], context: {
|
|
63
|
+
state: any;
|
|
64
|
+
}): EntryIssue[][] | Promise<EntryIssue[][]>;
|
|
65
|
+
}
|
|
66
|
+
export interface EntryTargetDeclaration {
|
|
67
|
+
key: string;
|
|
68
|
+
labelKey: string;
|
|
69
|
+
description: string;
|
|
70
|
+
fields: EntryField[];
|
|
71
|
+
mutation: string;
|
|
72
|
+
variables(row: EntryRow): Record<string, unknown>;
|
|
73
|
+
appliedOf?(data: any, row: EntryRow): {
|
|
74
|
+
id?: string;
|
|
75
|
+
label?: string;
|
|
76
|
+
} | undefined;
|
|
77
|
+
maxRows?: number;
|
|
78
|
+
check?(rows: EntryRow[], context: {
|
|
79
|
+
state: any;
|
|
80
|
+
}): EntryIssue[][] | Promise<EntryIssue[][]>;
|
|
81
|
+
update?: EntryUpdate;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-target-shape.js","sourceRoot":"","sources":["../../server/ai-entry/entry-target-shape.ts"],"names":[],"mappings":"","sourcesContent":["/*\n * The shape of an AI 입력 (data entry) target - the same shape as `EntryTarget` in\n * @things-factory/ai-assistant (server/service/data-entry/entry-target.ts).\n *\n * Kept here as a structural copy so that this package declares its targets as plain data and does not\n * depend on the assistant at run time. An application that runs the assistant passes these declarations\n * to `registerEntryTarget`; the assistant validates them at boot.\n */\nexport type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference'\n\nexport interface EntryReference {\n query: string\n variables(text: string): Record<string, unknown>\n recordsOf(data: any, text: string): { id: string; label?: string }[]\n}\n\nexport interface EntryField {\n name: string\n type: EntryFieldType\n required?: boolean\n values?: string[]\n description?: string\n labelKey?: string\n total?: boolean\n valueKeyPrefix?: string\n reference?: EntryReference\n}\n\nexport interface EntryIssue {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n blocking: boolean\n}\n\nexport interface EntryRow {\n index: number\n values: Record<string, unknown>\n sources: unknown[]\n issues: EntryIssue[]\n}\n\nexport type EntryRecord = { id: string } & Record<string, unknown>\n\nexport interface EntryUpdate {\n read: { query: string; variables(ids: string[]): Record<string, unknown>; recordsOf(data: any, ids: string[]): EntryRecord[] }\n find?: {\n query: string\n variables(criteria: Record<string, unknown>): Record<string, unknown>\n recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[]\n criteria: EntryField[]\n }\n mutation: string\n variables(change: { id: string; record: EntryRecord; values: Record<string, unknown> }): Record<string, unknown>\n labelOf?(record: EntryRecord): string\n fields?: string[]\n /** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */\n check?(rows: { id: string; record?: EntryRecord; values: Record<string, unknown> }[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n}\n\nexport interface EntryTargetDeclaration {\n key: string\n labelKey: string\n description: string\n fields: EntryField[]\n mutation: string\n variables(row: EntryRow): Record<string, unknown>\n appliedOf?(data: any, row: EntryRow): { id?: string; label?: string } | undefined\n maxRows?: number\n check?(rows: EntryRow[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n update?: EntryUpdate\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EntryIssue, EntryRecord } from './entry-target-shape';
|
|
2
|
+
type Row = {
|
|
3
|
+
values: Record<string, unknown>;
|
|
4
|
+
};
|
|
5
|
+
/** A term already in the domain keeps the row out; so does a row that repeats an earlier row of the proposal. */
|
|
6
|
+
export declare function terminologyIssues(rows: Row[], existing: {
|
|
7
|
+
locale?: string | null;
|
|
8
|
+
category?: string | null;
|
|
9
|
+
name?: string | null;
|
|
10
|
+
}[]): EntryIssue[][];
|
|
11
|
+
/** Only the fields given, among those named. */
|
|
12
|
+
export declare function inputOf(values: Record<string, unknown>, names: string[]): Record<string, unknown>;
|
|
13
|
+
/** The records whose every given criterion is a piece of the field. */
|
|
14
|
+
export declare function matching(records: EntryRecord[], criteria: Record<string, unknown>): EntryRecord[];
|
|
15
|
+
/** "ko-KR · label.department → 부서" */
|
|
16
|
+
export declare function terminologyLabel(record: Record<string, unknown>): string;
|
|
17
|
+
/**
|
|
18
|
+
* A change of a term against the other terms of the domain and the other changes of the card: a (locale, category,
|
|
19
|
+
* name) another term already has (the record itself excepted), or an earlier change gives, keeps it out.
|
|
20
|
+
*/
|
|
21
|
+
export declare function terminologyChangeIssues(rows: {
|
|
22
|
+
id: string;
|
|
23
|
+
record?: Record<string, unknown>;
|
|
24
|
+
values: Record<string, unknown>;
|
|
25
|
+
}[], existing: {
|
|
26
|
+
id: string;
|
|
27
|
+
locale?: string | null;
|
|
28
|
+
category?: string | null;
|
|
29
|
+
name?: string | null;
|
|
30
|
+
}[]): EntryIssue[][];
|
|
31
|
+
/** The names a change leaves its term with - the ones to read the domain's terms by. */
|
|
32
|
+
export declare function changedNames(rows: {
|
|
33
|
+
record?: Record<string, unknown>;
|
|
34
|
+
values: Record<string, unknown>;
|
|
35
|
+
}[]): string[];
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.terminologyIssues = terminologyIssues;
|
|
4
|
+
exports.inputOf = inputOf;
|
|
5
|
+
exports.matching = matching;
|
|
6
|
+
exports.terminologyLabel = terminologyLabel;
|
|
7
|
+
exports.terminologyChangeIssues = terminologyChangeIssues;
|
|
8
|
+
exports.changedNames = changedNames;
|
|
9
|
+
const text = (value) => (typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '');
|
|
10
|
+
/** (locale, category, name) is unique in a domain (ix_term_0) - the key a translation is looked up by. */
|
|
11
|
+
const keyOf = (one) => {
|
|
12
|
+
const [locale, category, name] = [text(one.locale), text(one.category), text(one.name)];
|
|
13
|
+
return locale && category && name ? `${locale}\u0000${category}\u0000${name}` : '';
|
|
14
|
+
};
|
|
15
|
+
/** A term already in the domain keeps the row out; so does a row that repeats an earlier row of the proposal. */
|
|
16
|
+
function terminologyIssues(rows, existing) {
|
|
17
|
+
const known = new Set(existing.map(keyOf).filter(Boolean));
|
|
18
|
+
return rows.map((row, i) => {
|
|
19
|
+
const key = keyOf(row.values);
|
|
20
|
+
if (!key)
|
|
21
|
+
return [];
|
|
22
|
+
if (known.has(key))
|
|
23
|
+
return [{ field: 'name', code: 'duplicate', messageKey: 'text.i18n-entry-terminology-exists', blocking: true }];
|
|
24
|
+
if (rows.slice(0, i).some(other => keyOf(other.values) === key))
|
|
25
|
+
return [{ field: 'name', code: 'duplicate-in-proposal', messageKey: 'text.i18n-entry-terminology-repeated', blocking: true }];
|
|
26
|
+
return [];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Only the fields given, among those named. */
|
|
30
|
+
function inputOf(values, names) {
|
|
31
|
+
return Object.fromEntries(names.filter(name => values[name] !== undefined && name in values).map(name => [name, values[name]]));
|
|
32
|
+
}
|
|
33
|
+
/** The records whose every given criterion is a piece of the field. */
|
|
34
|
+
function matching(records, criteria) {
|
|
35
|
+
const given = Object.entries(criteria ?? {}).filter(([, value]) => text(value));
|
|
36
|
+
return records.filter(record => given.every(([name, value]) => text(record[name]).toLowerCase().includes(text(value).toLowerCase())));
|
|
37
|
+
}
|
|
38
|
+
/** "ko-KR · label.department → 부서" */
|
|
39
|
+
function terminologyLabel(record) {
|
|
40
|
+
const key = [text(record.category), text(record.name)].filter(Boolean).join('.');
|
|
41
|
+
return [text(record.locale), [key, text(record.display)].filter(Boolean).join(' → ')].filter(Boolean).join(' · ');
|
|
42
|
+
}
|
|
43
|
+
/** A changed term's (locale, category, name) - the record's, with what the change gives in their place. */
|
|
44
|
+
const changedKeyOf = (row) => ['locale', 'category', 'name'].some(name => name in row.values) ? keyOf({ ...(row.record ?? {}), ...row.values }) : '';
|
|
45
|
+
/**
|
|
46
|
+
* A change of a term against the other terms of the domain and the other changes of the card: a (locale, category,
|
|
47
|
+
* name) another term already has (the record itself excepted), or an earlier change gives, keeps it out.
|
|
48
|
+
*/
|
|
49
|
+
function terminologyChangeIssues(rows, existing) {
|
|
50
|
+
return rows.map((row, i) => {
|
|
51
|
+
const key = changedKeyOf(row);
|
|
52
|
+
if (!key)
|
|
53
|
+
return [];
|
|
54
|
+
if (existing.some(one => one.id !== row.id && keyOf(one) === key))
|
|
55
|
+
return [{ field: 'name', code: 'duplicate', messageKey: 'text.i18n-entry-terminology-exists', blocking: true }];
|
|
56
|
+
if (rows.slice(0, i).some(other => other.id !== row.id && changedKeyOf(other) === key))
|
|
57
|
+
return [{ field: 'name', code: 'duplicate-in-proposal', messageKey: 'text.i18n-entry-terminology-repeated', blocking: true }];
|
|
58
|
+
return [];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** The names a change leaves its term with - the ones to read the domain's terms by. */
|
|
62
|
+
function changedNames(rows) {
|
|
63
|
+
return [...new Set(rows.filter(row => changedKeyOf(row)).map(row => text(row.values.name ?? row.record?.name)).filter(Boolean))];
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=terminology-entry-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminology-entry-rules.js","sourceRoot":"","sources":["../../server/ai-entry/terminology-entry-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The rules a proposed terminology row passes - pure, so they are tested without a server.\n */\nimport type { EntryIssue, EntryRecord } from './entry-target-shape'\n\ntype Row = { values: Record<string, unknown> }\n\nconst text = (value: unknown) => (typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '')\n\n/** (locale, category, name) is unique in a domain (ix_term_0) - the key a translation is looked up by. */\nconst keyOf = (one: { locale?: unknown; category?: unknown; name?: unknown }) => {\n const [locale, category, name] = [text(one.locale), text(one.category), text(one.name)]\n return locale && category && name ? `${locale}\\u0000${category}\\u0000${name}` : ''\n}\n\n/** A term already in the domain keeps the row out; so does a row that repeats an earlier row of the proposal. */\nexport function terminologyIssues(rows: Row[], existing: { locale?: string | null; category?: string | null; name?: string | null }[]): EntryIssue[][] {\n const known = new Set(existing.map(keyOf).filter(Boolean))\n return rows.map((row, i) => {\n const key = keyOf(row.values)\n if (!key) return []\n if (known.has(key)) return [{ field: 'name', code: 'duplicate', messageKey: 'text.i18n-entry-terminology-exists', blocking: true }]\n if (rows.slice(0, i).some(other => keyOf(other.values) === key))\n return [{ field: 'name', code: 'duplicate-in-proposal', messageKey: 'text.i18n-entry-terminology-repeated', blocking: true }]\n return []\n })\n}\n\n/** Only the fields given, among those named. */\nexport function inputOf(values: Record<string, unknown>, names: string[]): Record<string, unknown> {\n return Object.fromEntries(names.filter(name => values[name] !== undefined && name in values).map(name => [name, values[name]]))\n}\n\n/** The records whose every given criterion is a piece of the field. */\nexport function matching(records: EntryRecord[], criteria: Record<string, unknown>): EntryRecord[] {\n const given = Object.entries(criteria ?? {}).filter(([, value]) => text(value))\n return records.filter(record => given.every(([name, value]) => text(record[name]).toLowerCase().includes(text(value).toLowerCase())))\n}\n\n/** \"ko-KR · label.department → 부서\" */\nexport function terminologyLabel(record: Record<string, unknown>): string {\n const key = [text(record.category), text(record.name)].filter(Boolean).join('.')\n return [text(record.locale), [key, text(record.display)].filter(Boolean).join(' → ')].filter(Boolean).join(' · ')\n}\n\n/** A changed term's (locale, category, name) - the record's, with what the change gives in their place. */\nconst changedKeyOf = (row: { record?: Record<string, unknown>; values: Record<string, unknown> }) =>\n ['locale', 'category', 'name'].some(name => name in row.values) ? keyOf({ ...(row.record ?? {}), ...row.values }) : ''\n\n/**\n * A change of a term against the other terms of the domain and the other changes of the card: a (locale, category,\n * name) another term already has (the record itself excepted), or an earlier change gives, keeps it out.\n */\nexport function terminologyChangeIssues(\n rows: { id: string; record?: Record<string, unknown>; values: Record<string, unknown> }[],\n existing: { id: string; locale?: string | null; category?: string | null; name?: string | null }[]\n): EntryIssue[][] {\n return rows.map((row, i) => {\n const key = changedKeyOf(row)\n if (!key) return []\n if (existing.some(one => one.id !== row.id && keyOf(one) === key))\n return [{ field: 'name', code: 'duplicate', messageKey: 'text.i18n-entry-terminology-exists', blocking: true }]\n if (rows.slice(0, i).some(other => other.id !== row.id && changedKeyOf(other) === key))\n return [{ field: 'name', code: 'duplicate-in-proposal', messageKey: 'text.i18n-entry-terminology-repeated', blocking: true }]\n return []\n })\n}\n\n/** The names a change leaves its term with - the ones to read the domain's terms by. */\nexport function changedNames(rows: { record?: Record<string, unknown>; values: Record<string, unknown> }[]): string[] {\n return [...new Set(rows.filter(row => changedKeyOf(row)).map(row => text(row.values.name ?? row.record?.name)).filter(Boolean))]\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EntryTargetDeclaration } from './entry-target-shape';
|
|
2
|
+
export declare const I18N_TERMINOLOGY = "i18n-base.terminology";
|
|
3
|
+
/** The categories the terminology screen offers - a term is read as `<category>.<name>`. */
|
|
4
|
+
export declare const TERMINOLOGY_CATEGORIES: string[];
|
|
5
|
+
export declare function terminologyEntryTarget(options?: {
|
|
6
|
+
locales?: string[];
|
|
7
|
+
}): EntryTargetDeclaration;
|
|
8
|
+
/** The i18n AI 입력 targets - the terminology. An application registers it when a mode names its key. */
|
|
9
|
+
export declare function terminologyEntryTargets(options?: {
|
|
10
|
+
locales?: string[];
|
|
11
|
+
}): EntryTargetDeclaration[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TERMINOLOGY_CATEGORIES = exports.I18N_TERMINOLOGY = void 0;
|
|
37
|
+
exports.terminologyEntryTarget = terminologyEntryTarget;
|
|
38
|
+
exports.terminologyEntryTargets = terminologyEntryTargets;
|
|
39
|
+
/*
|
|
40
|
+
* AI 입력 target of the terminology - the domain's own words for the screens' keys (용어), entered from a
|
|
41
|
+
* glossary or a list the person pastes (operato-application/design/ai/data-entry.md).
|
|
42
|
+
*
|
|
43
|
+
* A plain declaration: this package does not depend on @things-factory/ai-assistant. An application that runs
|
|
44
|
+
* the assistant passes it to `registerEntryTarget` and names its key in a mode. Rows are entered through
|
|
45
|
+
* `createTerminology` and changed through `updateTerminology` (terminology:mutation), run as the person.
|
|
46
|
+
*
|
|
47
|
+
* The read of the update (`terminologies`) carries no @privilege today - anyone signed in to the domain may
|
|
48
|
+
* read the terms. The update's doors are the mutation and that query.
|
|
49
|
+
*/
|
|
50
|
+
const env_1 = require("@things-factory/env");
|
|
51
|
+
const terminology_entry_rules_1 = require("./terminology-entry-rules");
|
|
52
|
+
exports.I18N_TERMINOLOGY = 'i18n-base.terminology';
|
|
53
|
+
/** The categories the terminology screen offers - a term is read as `<category>.<name>`. */
|
|
54
|
+
exports.TERMINOLOGY_CATEGORIES = ['button', 'field', 'label', 'menu', 'text', 'title'];
|
|
55
|
+
/** The languages the application runs - `i18n.languages` of its config, as the terminology screen lists them. */
|
|
56
|
+
const DEFAULT_LOCALES = ['en-US', 'ko-KR', 'zh-CN', 'ja-JP', 'ms-MY'];
|
|
57
|
+
function configuredLocales() {
|
|
58
|
+
const languages = env_1.config.get('i18n/languages', null);
|
|
59
|
+
const codes = Array.isArray(languages) ? languages.map(one => one?.code).filter((code) => typeof code === 'string' && !!code) : [];
|
|
60
|
+
return codes.length ? codes : DEFAULT_LOCALES;
|
|
61
|
+
}
|
|
62
|
+
const TERMINOLOGIES = 'query ($filters: [Filter!], $pagination: Pagination) { terminologies(filters: $filters, pagination: $pagination) { items { id locale category name display description } } }';
|
|
63
|
+
const FIELDS = ['locale', 'category', 'name', 'display', 'description'];
|
|
64
|
+
function terminologyEntryTarget(options = {}) {
|
|
65
|
+
const locales = options.locales?.length ? options.locales : configuredLocales();
|
|
66
|
+
const fields = [
|
|
67
|
+
{ name: 'locale', type: 'enum', required: true, values: locales, labelKey: 'field.locale', description: `the language the display is in - ${locales.join(', ')}` },
|
|
68
|
+
{
|
|
69
|
+
name: 'category',
|
|
70
|
+
type: 'enum',
|
|
71
|
+
required: true,
|
|
72
|
+
values: exports.TERMINOLOGY_CATEGORIES,
|
|
73
|
+
labelKey: 'field.category',
|
|
74
|
+
description: 'the kind of screen word: button, field (a column or input), label, menu, text (a sentence), title'
|
|
75
|
+
},
|
|
76
|
+
{ name: 'name', type: 'string', required: true, labelKey: 'field.name', description: 'the key after the category, as the screens use it - "department" for label.department' },
|
|
77
|
+
{ name: 'display', type: 'string', required: true, labelKey: 'field.display', description: 'what the screen shows in that language' },
|
|
78
|
+
{ name: 'description', type: 'string', labelKey: 'field.description' }
|
|
79
|
+
];
|
|
80
|
+
return {
|
|
81
|
+
key: exports.I18N_TERMINOLOGY,
|
|
82
|
+
labelKey: 'label.i18n-entry-terminology',
|
|
83
|
+
description: 'one term of the screens in one language: the language, the category, the key name, and the word to show.',
|
|
84
|
+
mutation: 'mutation ($terminology: NewTerminology!) { createTerminology(terminology: $terminology) { id locale category name display } }',
|
|
85
|
+
fields,
|
|
86
|
+
maxRows: 200,
|
|
87
|
+
variables: (row) => ({ terminology: (0, terminology_entry_rules_1.inputOf)(row.values, FIELDS) }),
|
|
88
|
+
appliedOf: (data, row) => ({ id: data?.createTerminology?.id, label: (0, terminology_entry_rules_1.terminologyLabel)(data?.createTerminology ?? row.values) }),
|
|
89
|
+
/* Read through the repository of the request's domain only, imported when it runs. */
|
|
90
|
+
check: async (rows, { state }) => {
|
|
91
|
+
const domainId = state?.domain?.id;
|
|
92
|
+
const names = [...new Set(rows.map(row => row.values.name).filter((v) => typeof v === 'string' && !!v.trim()).map(v => v.trim()))];
|
|
93
|
+
if (!domainId || !names.length)
|
|
94
|
+
return (0, terminology_entry_rules_1.terminologyIssues)(rows, []);
|
|
95
|
+
const [{ getRepository }, { Terminology }, { In }] = await Promise.all([Promise.resolve().then(() => __importStar(require('@things-factory/shell'))), Promise.resolve().then(() => __importStar(require('../service/terminology/terminology'))), Promise.resolve().then(() => __importStar(require('typeorm')))]);
|
|
96
|
+
const existing = await getRepository(Terminology).find({ where: { domain: { id: domainId }, name: In(names) }, select: ['id', 'locale', 'category', 'name'] });
|
|
97
|
+
return (0, terminology_entry_rules_1.terminologyIssues)(rows, existing);
|
|
98
|
+
},
|
|
99
|
+
update: {
|
|
100
|
+
read: {
|
|
101
|
+
query: TERMINOLOGIES,
|
|
102
|
+
variables: ids => ({ filters: [{ name: 'id', operator: 'in', value: ids }], pagination: { page: 1, limit: ids.length } }),
|
|
103
|
+
recordsOf: data => data?.terminologies?.items ?? []
|
|
104
|
+
},
|
|
105
|
+
find: {
|
|
106
|
+
query: TERMINOLOGIES,
|
|
107
|
+
variables: criteria => ({
|
|
108
|
+
filters: ['name', 'category', 'display'].filter(name => typeof criteria?.[name] === 'string' && criteria[name]).map(name => ({ name, operator: 'search', value: criteria[name] })),
|
|
109
|
+
pagination: { page: 1, limit: 200 }
|
|
110
|
+
}),
|
|
111
|
+
recordsOf: (data, criteria) => (0, terminology_entry_rules_1.matching)(data?.terminologies?.items ?? [], criteria),
|
|
112
|
+
criteria: [
|
|
113
|
+
{ name: 'name', type: 'string', description: 'a piece of the key name' },
|
|
114
|
+
{ name: 'category', type: 'string', description: 'the category' },
|
|
115
|
+
{ name: 'display', type: 'string', description: 'a piece of the word shown' }
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
mutation: 'mutation ($id: String!, $patch: TerminologyPatch!) { updateTerminology(id: $id, patch: $patch) { id } }',
|
|
119
|
+
variables: ({ id, values }) => ({ id, patch: (0, terminology_entry_rules_1.inputOf)(values, FIELDS) }),
|
|
120
|
+
labelOf: record => (0, terminology_entry_rules_1.terminologyLabel)(record),
|
|
121
|
+
fields: FIELDS,
|
|
122
|
+
/* A (locale, category, name) another term of the domain already has - the record itself excepted. */
|
|
123
|
+
check: async (rows, { state }) => {
|
|
124
|
+
const domainId = state?.domain?.id;
|
|
125
|
+
const names = (0, terminology_entry_rules_1.changedNames)(rows);
|
|
126
|
+
if (!domainId || !names.length)
|
|
127
|
+
return (0, terminology_entry_rules_1.terminologyChangeIssues)(rows, []);
|
|
128
|
+
const [{ getRepository }, { Terminology }, { In }] = await Promise.all([Promise.resolve().then(() => __importStar(require('@things-factory/shell'))), Promise.resolve().then(() => __importStar(require('../service/terminology/terminology'))), Promise.resolve().then(() => __importStar(require('typeorm')))]);
|
|
129
|
+
const existing = await getRepository(Terminology).find({ where: { domain: { id: domainId }, name: In(names) }, select: ['id', 'locale', 'category', 'name'] });
|
|
130
|
+
return (0, terminology_entry_rules_1.terminologyChangeIssues)(rows, existing);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** The i18n AI 입력 targets - the terminology. An application registers it when a mode names its key. */
|
|
136
|
+
function terminologyEntryTargets(options = {}) {
|
|
137
|
+
return [terminologyEntryTarget(options)];
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=terminology-entry-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminology-entry-targets.js","sourceRoot":"","sources":["../../server/ai-entry/terminology-entry-targets.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * AI 입력 target of the terminology - the domain's own words for the screens' keys (용어), entered from a\n * glossary or a list the person pastes (operato-application/design/ai/data-entry.md).\n *\n * A plain declaration: this package does not depend on @things-factory/ai-assistant. An application that runs\n * the assistant passes it to `registerEntryTarget` and names its key in a mode. Rows are entered through\n * `createTerminology` and changed through `updateTerminology` (terminology:mutation), run as the person.\n *\n * The read of the update (`terminologies`) carries no @privilege today - anyone signed in to the domain may\n * read the terms. The update's doors are the mutation and that query.\n */\nimport { config } from '@things-factory/env'\n\nimport type { EntryField, EntryRow, EntryTargetDeclaration } from './entry-target-shape'\nimport { changedNames, inputOf, matching, terminologyChangeIssues, terminologyIssues, terminologyLabel } from './terminology-entry-rules'\n\nexport const I18N_TERMINOLOGY = 'i18n-base.terminology'\n\n/** The categories the terminology screen offers - a term is read as `<category>.<name>`. */\nexport const TERMINOLOGY_CATEGORIES = ['button', 'field', 'label', 'menu', 'text', 'title']\n\n/** The languages the application runs - `i18n.languages` of its config, as the terminology screen lists them. */\nconst DEFAULT_LOCALES = ['en-US', 'ko-KR', 'zh-CN', 'ja-JP', 'ms-MY']\n\nfunction configuredLocales(): string[] {\n const languages = config.get('i18n/languages', null) as { code?: string }[] | null\n const codes = Array.isArray(languages) ? languages.map(one => one?.code).filter((code): code is string => typeof code === 'string' && !!code) : []\n return codes.length ? codes : DEFAULT_LOCALES\n}\n\nconst TERMINOLOGIES =\n 'query ($filters: [Filter!], $pagination: Pagination) { terminologies(filters: $filters, pagination: $pagination) { items { id locale category name display description } } }'\n\nconst FIELDS = ['locale', 'category', 'name', 'display', 'description']\n\nexport function terminologyEntryTarget(options: { locales?: string[] } = {}): EntryTargetDeclaration {\n const locales = options.locales?.length ? options.locales : configuredLocales()\n const fields: EntryField[] = [\n { name: 'locale', type: 'enum', required: true, values: locales, labelKey: 'field.locale', description: `the language the display is in - ${locales.join(', ')}` },\n {\n name: 'category',\n type: 'enum',\n required: true,\n values: TERMINOLOGY_CATEGORIES,\n labelKey: 'field.category',\n description: 'the kind of screen word: button, field (a column or input), label, menu, text (a sentence), title'\n },\n { name: 'name', type: 'string', required: true, labelKey: 'field.name', description: 'the key after the category, as the screens use it - \"department\" for label.department' },\n { name: 'display', type: 'string', required: true, labelKey: 'field.display', description: 'what the screen shows in that language' },\n { name: 'description', type: 'string', labelKey: 'field.description' }\n ]\n\n return {\n key: I18N_TERMINOLOGY,\n labelKey: 'label.i18n-entry-terminology',\n description: 'one term of the screens in one language: the language, the category, the key name, and the word to show.',\n mutation: 'mutation ($terminology: NewTerminology!) { createTerminology(terminology: $terminology) { id locale category name display } }',\n fields,\n maxRows: 200,\n variables: (row: EntryRow) => ({ terminology: inputOf(row.values, FIELDS) }),\n appliedOf: (data, row) => ({ id: data?.createTerminology?.id, label: terminologyLabel(data?.createTerminology ?? row.values) }),\n\n /* Read through the repository of the request's domain only, imported when it runs. */\n check: async (rows, { state }) => {\n const domainId = state?.domain?.id\n const names = [...new Set(rows.map(row => row.values.name).filter((v): v is string => typeof v === 'string' && !!v.trim()).map(v => v.trim()))]\n if (!domainId || !names.length) return terminologyIssues(rows, [])\n const [{ getRepository }, { Terminology }, { In }] = await Promise.all([import('@things-factory/shell'), import('../service/terminology/terminology'), import('typeorm')])\n const existing = await getRepository(Terminology).find({ where: { domain: { id: domainId }, name: In(names) } as any, select: ['id', 'locale', 'category', 'name'] as any })\n return terminologyIssues(rows, existing)\n },\n\n update: {\n read: {\n query: TERMINOLOGIES,\n variables: ids => ({ filters: [{ name: 'id', operator: 'in', value: ids }], pagination: { page: 1, limit: ids.length } }),\n recordsOf: data => data?.terminologies?.items ?? []\n },\n find: {\n query: TERMINOLOGIES,\n variables: criteria => ({\n filters: ['name', 'category', 'display'].filter(name => typeof criteria?.[name] === 'string' && criteria[name]).map(name => ({ name, operator: 'search', value: criteria[name] })),\n pagination: { page: 1, limit: 200 }\n }),\n recordsOf: (data, criteria) => matching(data?.terminologies?.items ?? [], criteria),\n criteria: [\n { name: 'name', type: 'string', description: 'a piece of the key name' },\n { name: 'category', type: 'string', description: 'the category' },\n { name: 'display', type: 'string', description: 'a piece of the word shown' }\n ]\n },\n mutation: 'mutation ($id: String!, $patch: TerminologyPatch!) { updateTerminology(id: $id, patch: $patch) { id } }',\n variables: ({ id, values }) => ({ id, patch: inputOf(values, FIELDS) }),\n labelOf: record => terminologyLabel(record),\n fields: FIELDS,\n\n /* A (locale, category, name) another term of the domain already has - the record itself excepted. */\n check: async (rows, { state }) => {\n const domainId = state?.domain?.id\n const names = changedNames(rows)\n if (!domainId || !names.length) return terminologyChangeIssues(rows, [])\n const [{ getRepository }, { Terminology }, { In }] = await Promise.all([import('@things-factory/shell'), import('../service/terminology/terminology'), import('typeorm')])\n const existing = await getRepository(Terminology).find({ where: { domain: { id: domainId }, name: In(names) } as any, select: ['id', 'locale', 'category', 'name'] as any })\n return terminologyChangeIssues(rows, existing as any)\n }\n }\n }\n}\n\n/** The i18n AI 입력 targets - the terminology. An application registers it when a mode names its key. */\nexport function terminologyEntryTargets(options: { locales?: string[] } = {}): EntryTargetDeclaration[] {\n return [terminologyEntryTarget(options)]\n}\n"]}
|
package/dist-server/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export * from './service';
|
|
|
3
3
|
export * from './apis';
|
|
4
4
|
export * from './middlewares';
|
|
5
5
|
export * from './controllers/clear-cache-for-translation.js';
|
|
6
|
+
export * from './ai-entry/terminology-entry-targets';
|
|
7
|
+
export type { EntryTargetDeclaration as TerminologyEntryTarget } from './ai-entry/entry-target-shape';
|
package/dist-server/index.js
CHANGED
|
@@ -6,4 +6,5 @@ tslib_1.__exportStar(require("./service"), exports);
|
|
|
6
6
|
tslib_1.__exportStar(require("./apis"), exports);
|
|
7
7
|
tslib_1.__exportStar(require("./middlewares"), exports);
|
|
8
8
|
tslib_1.__exportStar(require("./controllers/clear-cache-for-translation.js"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./ai-entry/terminology-entry-targets"), exports);
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
package/dist-server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,oBAAiB;AAEjB,oDAAyB;AACzB,iDAAsB;AACtB,wDAA6B;AAC7B,uFAA4D","sourcesContent":["import './routes'\n\nexport * from './service'\nexport * from './apis'\nexport * from './middlewares'\nexport * from './controllers/clear-cache-for-translation.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA,oBAAiB;AAEjB,oDAAyB;AACzB,iDAAsB;AACtB,wDAA6B;AAC7B,uFAA4D;AAC5D,+EAAoD","sourcesContent":["import './routes'\n\nexport * from './service'\nexport * from './apis'\nexport * from './middlewares'\nexport * from './controllers/clear-cache-for-translation.js'\nexport * from './ai-entry/terminology-entry-targets'\nexport type { EntryTargetDeclaration as TerminologyEntryTarget } from './ai-entry/entry-target-shape'\n"]}
|