@valbuild/core 0.126.0 → 0.127.0
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/CHANGELOG.md +184 -0
- package/dist/declarations/src/index.d.ts +5 -1
- package/dist/declarations/src/initSchema.d.ts +16 -0
- package/dist/declarations/src/locale.d.ts +55 -0
- package/dist/declarations/src/schema/array.d.ts +4 -0
- package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
- package/dist/declarations/src/schema/discriminatedUnion.d.ts +4 -0
- package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
- package/dist/declarations/src/schema/enum.d.ts +9 -0
- package/dist/declarations/src/schema/index.d.ts +116 -3
- package/dist/declarations/src/schema/literal.d.ts +2 -0
- package/dist/declarations/src/schema/locale.d.ts +132 -0
- package/dist/declarations/src/schema/object.d.ts +7 -0
- package/dist/declarations/src/schema/record.d.ts +51 -1
- package/dist/declarations/src/schema/settings.d.ts +54 -1
- package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
- package/dist/declarations/src/source/settings.d.ts +51 -0
- package/dist/{index-cd25c2dc.esm.js → index-5a6e6398.esm.js} +1035 -54
- package/dist/{index-b41b638f.cjs.dev.js → index-889ac441.cjs.dev.js} +1040 -53
- package/dist/{index-6db78f3c.cjs.prod.js → index-d44deccb.cjs.prod.js} +1040 -53
- package/dist/valbuild-core.cjs.dev.js +7 -1
- package/dist/valbuild-core.cjs.prod.js +7 -1
- package/dist/valbuild-core.esm.js +1 -1
- package/package.json +1 -1
- package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
- package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
- package/patch/dist/valbuild-core-patch.esm.js +2 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
+
import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
|
|
3
|
+
import { FieldRender } from "../render.js";
|
|
4
|
+
import { SourcePath } from "../val/index.js";
|
|
5
|
+
import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
|
|
6
|
+
export type SerializedLocaleSchema = {
|
|
7
|
+
type: "locale";
|
|
8
|
+
/** Static layout config, carried whole in the serialized schema — see `render.ts`. */
|
|
9
|
+
render?: FieldRender;
|
|
10
|
+
/** Set when this schema declares a `preview`. The closure itself cannot serialize. */
|
|
11
|
+
preview?: true;
|
|
12
|
+
opt: boolean;
|
|
13
|
+
customValidate?: boolean;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
description?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* One of the languages the project publishes.
|
|
20
|
+
*
|
|
21
|
+
* The set is NOT here. It is declared in the settings module — `locales.available`
|
|
22
|
+
* — so that adding a language is something the people who write the content can
|
|
23
|
+
* do, and so that one list drives validation, the Studio's picker and the
|
|
24
|
+
* translation notes. This schema knows only that the value is one of them, which
|
|
25
|
+
* is checked cross-module through the `locale:check-locale` fix, exactly as
|
|
26
|
+
* `keyOf` and `route` check theirs.
|
|
27
|
+
*
|
|
28
|
+
* Never stega encoded (see `stegaEncode.ts`): a locale goes into `<html lang>`,
|
|
29
|
+
* into `hreflang` and into `Intl` constructors, none of which survive invisible
|
|
30
|
+
* characters.
|
|
31
|
+
*/
|
|
32
|
+
export declare class LocaleSchema<Src extends string | null> extends Schema<Src> {
|
|
33
|
+
private readonly opt;
|
|
34
|
+
private readonly customValidateFunctions;
|
|
35
|
+
private readonly isReadonly;
|
|
36
|
+
private readonly isHidden;
|
|
37
|
+
private readonly description?;
|
|
38
|
+
private readonly renderInput;
|
|
39
|
+
private readonly previewInput;
|
|
40
|
+
/**
|
|
41
|
+
* Type-only marker: as a record key, this schema DECLARES the key set.
|
|
42
|
+
*
|
|
43
|
+
* `declare` so it exists in the type and nowhere at runtime — reading it off
|
|
44
|
+
* an instance gives `undefined`, and nothing should. It is how `record()`
|
|
45
|
+
* knows to type entries as `Item | null`: a language nobody has translated
|
|
46
|
+
* into is a null entry, not an absent key. See `DeclaredKeySet` and
|
|
47
|
+
* `RecordSrcOf`.
|
|
48
|
+
*/
|
|
49
|
+
readonly __declaresRecordKeys: true;
|
|
50
|
+
constructor(opt?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
|
|
51
|
+
/**
|
|
52
|
+
* A note to whoever edits this field, shown under it in the Studio.
|
|
53
|
+
*
|
|
54
|
+
* Pass `null` to clear a description set earlier.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* const schema = s.object({
|
|
58
|
+
* locale: s.locale().describe("The language everything below is written in"),
|
|
59
|
+
* title: s.string(),
|
|
60
|
+
* });
|
|
61
|
+
* export default c.define("/example.val.ts", schema, {
|
|
62
|
+
* locale: "nb-NO",
|
|
63
|
+
* title: "Vinterjakke",
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
66
|
+
describe(description: string | null): LocaleSchema<Src>;
|
|
67
|
+
/**
|
|
68
|
+
* A check of your own, on top of "is this one of the project's languages".
|
|
69
|
+
*
|
|
70
|
+
* Return a message when the value is WRONG and `false` when it is fine — the
|
|
71
|
+
* two answers are a complaint and no complaint, so write the check as a
|
|
72
|
+
* ternary rather than as `ok || "message"`, which returns `true` and `true`
|
|
73
|
+
* is neither.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const schema = s.object({
|
|
77
|
+
* locale: s.locale().validate((val) =>
|
|
78
|
+
* val === "en-US" ? "This section is not translated into English yet" : false,
|
|
79
|
+
* ),
|
|
80
|
+
* title: s.string(),
|
|
81
|
+
* });
|
|
82
|
+
* export default c.define("/example.val.ts", schema, {
|
|
83
|
+
* locale: "nb-NO",
|
|
84
|
+
* title: "Vinterjakke",
|
|
85
|
+
* });
|
|
86
|
+
*/
|
|
87
|
+
validate(validationFunction: (src: Src) => false | string): LocaleSchema<Src>;
|
|
88
|
+
protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
|
|
89
|
+
protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
|
|
90
|
+
nullable(): LocaleSchema<Src | null>;
|
|
91
|
+
readonly(): LocaleSchema<Src>;
|
|
92
|
+
hidden(): LocaleSchema<Src>;
|
|
93
|
+
protected isLocaleField(): boolean;
|
|
94
|
+
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
95
|
+
/**
|
|
96
|
+
* How this field is laid out where it is the item of an array or a record.
|
|
97
|
+
* Static configuration, not a callback — see `render.ts`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const schema = s.array(s.locale().render({ as: "inline" }));
|
|
101
|
+
* export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
|
|
102
|
+
*/
|
|
103
|
+
render(input: FieldRender): LocaleSchema<Src>;
|
|
104
|
+
/**
|
|
105
|
+
* How this VALUE is shown where a preview of it is needed. Never how the field
|
|
106
|
+
* itself is edited (that is `render`). See `preview.ts`.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* const schema = s.array(
|
|
110
|
+
* s.locale().preview(({ val }) => ({ title: val })),
|
|
111
|
+
* );
|
|
112
|
+
* export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
|
|
113
|
+
*/
|
|
114
|
+
preview(select: ItemPreviewInput<Src>): LocaleSchema<Src>;
|
|
115
|
+
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
116
|
+
protected declaresItemPreview(): boolean;
|
|
117
|
+
protected executeSerialize(): SerializedSchema;
|
|
118
|
+
protected executePreview(): ReifiedPreview;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Define one of the project's languages.
|
|
122
|
+
*
|
|
123
|
+
* The languages themselves are declared in the settings module, under
|
|
124
|
+
* `locales.available` — this says only that a value is one of them.
|
|
125
|
+
*
|
|
126
|
+
* @example // a field: everything under this object is in this language
|
|
127
|
+
* const schema = s.object({ locale: s.locale(), title: s.string() });
|
|
128
|
+
*
|
|
129
|
+
* @example // a key: one entry per language
|
|
130
|
+
* const schema = s.record(s.locale(), s.object({ title: s.string() }));
|
|
131
|
+
*/
|
|
132
|
+
export declare const locale: () => LocaleSchema<string>;
|
|
@@ -102,6 +102,13 @@ export declare class ObjectSchema<Props extends ObjectSchemaProps, Src extends O
|
|
|
102
102
|
nullable(): ObjectSchema<Props, Src | null>;
|
|
103
103
|
readonly(isReadonly?: boolean): ObjectSchema<Props, Src>;
|
|
104
104
|
hidden(isHidden?: boolean): ObjectSchema<Props, Src>;
|
|
105
|
+
/** The names of this object's `s.locale()` fields, in declaration order. */
|
|
106
|
+
protected localeFieldNames(): string[];
|
|
107
|
+
protected opensLocaleScope(): "field" | "key" | null;
|
|
108
|
+
protected localeScopeChildren(): {
|
|
109
|
+
key: string;
|
|
110
|
+
schema: Schema<SelectorSource>;
|
|
111
|
+
}[];
|
|
105
112
|
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
106
113
|
/**
|
|
107
114
|
* How this field is laid out in the editor when it is the item of an array
|
|
@@ -152,6 +152,37 @@ RO extends boolean = false> extends Schema<Src> {
|
|
|
152
152
|
*/
|
|
153
153
|
validate(validationFunction: (src: Src) => false | string): RecordSchema<T, K, Src>;
|
|
154
154
|
protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
|
|
155
|
+
/**
|
|
156
|
+
* The check that a record with a DECLARED key set holds every one of them.
|
|
157
|
+
*
|
|
158
|
+
* `s.record(s.locale(), item)` has one entry per language and
|
|
159
|
+
* `s.record(s.union(s.literal("a"), s.literal("b")), item)` has both — the
|
|
160
|
+
* keys are part of the schema either way, so a missing one is a hole in the
|
|
161
|
+
* content rather than content nobody has written yet. TypeScript already
|
|
162
|
+
* demanded both keys of the second; this is the validator catching up, which
|
|
163
|
+
* is why the two now behave the same.
|
|
164
|
+
*
|
|
165
|
+
* Deferred to `resolveSchemaSourceFixes` even for the literal case, where the
|
|
166
|
+
* keys are known right here: a locale record's keys are in another file, and
|
|
167
|
+
* one code path for both is one message, one fix and one thing to be wrong.
|
|
168
|
+
*/
|
|
169
|
+
private getDeclaredKeysValidation;
|
|
170
|
+
/**
|
|
171
|
+
* The key set this record's schema declares, computed once.
|
|
172
|
+
*
|
|
173
|
+
* `declaredKeySetOf` reads a SERIALIZED schema, and serializing the key
|
|
174
|
+
* schema walks it — twice per validate, and once per record instance, for an
|
|
175
|
+
* answer that is a property of the schema and cannot move between them. A
|
|
176
|
+
* schema is built once and validated against many times, so the walk happens
|
|
177
|
+
* on the first question and not on any after it.
|
|
178
|
+
*/
|
|
179
|
+
private declaredKeys;
|
|
180
|
+
private declaredKeySet;
|
|
181
|
+
protected opensLocaleScope(): "field" | "key" | null;
|
|
182
|
+
protected localeScopeChildren(): {
|
|
183
|
+
key: string;
|
|
184
|
+
schema: Schema<SelectorSource>;
|
|
185
|
+
}[];
|
|
155
186
|
private isRemoteUrl;
|
|
156
187
|
/** Marks the validation errors reported at `path` as key errors (in place). */
|
|
157
188
|
private markKeyErrorsAtPath;
|
|
@@ -344,6 +375,25 @@ RO extends boolean = false> extends Schema<Src> {
|
|
|
344
375
|
*/
|
|
345
376
|
render(input: FieldRender): RecordSchema<T, K, Src>;
|
|
346
377
|
}
|
|
347
|
-
|
|
378
|
+
/**
|
|
379
|
+
* The source of a record, given what its key schema says about its keys.
|
|
380
|
+
*
|
|
381
|
+
* A key schema that ENUMERATES its keys (a union of literals, or `s.locale()`)
|
|
382
|
+
* makes every one of them an entry of the record, and an entry nobody has
|
|
383
|
+
* written yet is `null` rather than absent — so the value type widens by
|
|
384
|
+
* `null`. That is the whole of option F at the type level: a half-translated
|
|
385
|
+
* record is a record with nulls in it, which is data you can count, filter and
|
|
386
|
+
* see in a diff, rather than keys that are simply not there.
|
|
387
|
+
*
|
|
388
|
+
* For a union of literals TypeScript already required every key, since
|
|
389
|
+
* `SelectorOfSchema<K>` is the literal union itself. `s.locale()` is a flavoured
|
|
390
|
+
* string, so it requires nothing here and the requirement is the validator's —
|
|
391
|
+
* deliberately: which languages exist is in the settings module, where the
|
|
392
|
+
* project can change it without a deploy.
|
|
393
|
+
*/
|
|
394
|
+
export type RecordSrcOf<K extends Schema<string>, S extends Schema<SelectorSource>> = K extends {
|
|
395
|
+
__declaresRecordKeys: true;
|
|
396
|
+
} ? Record<SelectorOfSchema<K>, SelectorOfSchema<S> | null> : Record<SelectorOfSchema<K>, SelectorOfSchema<S>>;
|
|
397
|
+
export declare function record<K extends Schema<string>, S extends Schema<SelectorSource>>(key: K, schema: S): RecordSchema<S, K, RecordSrcOf<K, S>>;
|
|
348
398
|
export declare function record<S extends Schema<SelectorSource>>(schema: S): RecordSchema<S, Schema<string>, Record<string, SelectorOfSchema<S>>>;
|
|
349
399
|
export {};
|
|
@@ -38,6 +38,19 @@ export type SerializedSettingsSchema = {
|
|
|
38
38
|
readonly?: boolean;
|
|
39
39
|
hidden?: boolean;
|
|
40
40
|
description?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Which of Val's sections this is, where it is one.
|
|
43
|
+
*
|
|
44
|
+
* The name rather than the rule itself, because the rule is a closure and a
|
|
45
|
+
* closure does not survive JSON. Val owns both ends — every section's rules
|
|
46
|
+
* are in {@link sectionValidators} in this file — so the name is enough for
|
|
47
|
+
* {@link deserializeSchema} to put the same rules back, and a Studio that
|
|
48
|
+
* does not recognise the name simply runs none (see `sectionValidate`).
|
|
49
|
+
*
|
|
50
|
+
* Absent on the settings module itself, which is a bag of sections and has no
|
|
51
|
+
* rules of its own.
|
|
52
|
+
*/
|
|
53
|
+
section?: string;
|
|
41
54
|
};
|
|
42
55
|
/**
|
|
43
56
|
* An object whose keys are all optional: absent means unset.
|
|
@@ -58,7 +71,47 @@ export declare class SettingsSchema<Src extends {
|
|
|
58
71
|
private readonly isReadonly;
|
|
59
72
|
private readonly isHidden;
|
|
60
73
|
private readonly description?;
|
|
61
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Which section this is, where Val has rules about it beyond its keys.
|
|
76
|
+
*
|
|
77
|
+
* Not the `validate` the class deliberately does not offer: that one is the
|
|
78
|
+
* schema author's, and `s.settings()` takes no arguments to declare it
|
|
79
|
+
* with. This names a rule Val states about a shape Val owns — no language
|
|
80
|
+
* declared twice in `locales.available` is true of every project, and
|
|
81
|
+
* there is nowhere else it can be said. A single value's rules stay on its
|
|
82
|
+
* own schema; this is for the ones that need to see more than one value,
|
|
83
|
+
* or to report on a row of a list.
|
|
84
|
+
*
|
|
85
|
+
* A NAME rather than the function, so it survives serialization: the schema
|
|
86
|
+
* reaches the Studio's validation worker as JSON, and a closure would be
|
|
87
|
+
* dropped on the way — which is how these rules once went missing there.
|
|
88
|
+
*/
|
|
89
|
+
private readonly section?;
|
|
90
|
+
constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined,
|
|
91
|
+
/**
|
|
92
|
+
* Which section this is, where Val has rules about it beyond its keys.
|
|
93
|
+
*
|
|
94
|
+
* Not the `validate` the class deliberately does not offer: that one is the
|
|
95
|
+
* schema author's, and `s.settings()` takes no arguments to declare it
|
|
96
|
+
* with. This names a rule Val states about a shape Val owns — no language
|
|
97
|
+
* declared twice in `locales.available` is true of every project, and
|
|
98
|
+
* there is nowhere else it can be said. A single value's rules stay on its
|
|
99
|
+
* own schema; this is for the ones that need to see more than one value,
|
|
100
|
+
* or to report on a row of a list.
|
|
101
|
+
*
|
|
102
|
+
* A NAME rather than the function, so it survives serialization: the schema
|
|
103
|
+
* reaches the Studio's validation worker as JSON, and a closure would be
|
|
104
|
+
* dropped on the way — which is how these rules once went missing there.
|
|
105
|
+
*/
|
|
106
|
+
section?: string | undefined);
|
|
107
|
+
/**
|
|
108
|
+
* This section's rules, or `undefined` where there are none to run.
|
|
109
|
+
*
|
|
110
|
+
* A name this Val does not know means a schema written by a newer one. Its
|
|
111
|
+
* per-field validation still runs; only the section's own rule is missed,
|
|
112
|
+
* which is the mild half of the failure and better than refusing the schema.
|
|
113
|
+
*/
|
|
114
|
+
private sectionValidate;
|
|
62
115
|
protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
|
|
63
116
|
protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
|
|
64
117
|
nullable(): SettingsSchema<Src | null>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "keyof:check-keys", "router:check-route", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "jsonValues:extract-entry", "external:upload"];
|
|
1
|
+
export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "keyof:check-keys", "router:check-route", "locale:check-locale", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "jsonValues:extract-entry", "record:fill-keys", "external:upload"];
|
|
2
2
|
export type ValidationFix = (typeof ValidationFix)[number];
|
|
@@ -22,6 +22,41 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export type SettingsSource = {
|
|
24
24
|
assistant?: AssistantSettingsSource;
|
|
25
|
+
locales?: LocalesSettingsSource;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The languages this project publishes.
|
|
29
|
+
*
|
|
30
|
+
* Content rather than configuration, and deliberately: which languages a site
|
|
31
|
+
* has is a decision the people who write it make, and under a build-time
|
|
32
|
+
* constant it took a developer and a deploy. It is the same move `assistant`
|
|
33
|
+
* makes with `enabled`.
|
|
34
|
+
*
|
|
35
|
+
* Val ships no list of its own. A project with no `locales` section has not
|
|
36
|
+
* said it is translated, and nothing about locales appears anywhere — no picker
|
|
37
|
+
* in the Studio, no checks, nothing.
|
|
38
|
+
*
|
|
39
|
+
* **This list is a decision with a blast radius.** Every locale in content is
|
|
40
|
+
* checked against it, so removing one invalidates the content that uses it, and
|
|
41
|
+
* adding one leaves every locale-keyed record short of a language until it is
|
|
42
|
+
* filled in. That is the intended behaviour — a language that is declared and
|
|
43
|
+
* missing everywhere is worth being told about — but it is why the Studio warns
|
|
44
|
+
* before saving a removal rather than treating this as an ordinary field.
|
|
45
|
+
*
|
|
46
|
+
* There is no default language, deliberately. Every locale-specific field asks
|
|
47
|
+
* which language it is in, and a default is exactly the answer that lets that
|
|
48
|
+
* question go unanswered — content ends up filed under a language nobody chose,
|
|
49
|
+
* which is the state translation is meant to make visible.
|
|
50
|
+
*/
|
|
51
|
+
export type LocalesSettingsSource = {
|
|
52
|
+
/**
|
|
53
|
+
* The languages, as canonical BCP 47 tags: `en-US`, `nb-NO`.
|
|
54
|
+
*
|
|
55
|
+
* Order is the project's own, and it is kept: it decides the order of the
|
|
56
|
+
* Studio's picker and of the rows in a locale-keyed record, so a project can
|
|
57
|
+
* put the language it works in first.
|
|
58
|
+
*/
|
|
59
|
+
available?: string[] | null;
|
|
25
60
|
};
|
|
26
61
|
/**
|
|
27
62
|
* The assistant: whether editors have one, and what it is told about the
|
|
@@ -55,6 +90,22 @@ export type AssistantSettingsSource = {
|
|
|
55
90
|
* `glossary`) rather than a field that looks like it should absorb them.
|
|
56
91
|
*/
|
|
57
92
|
tone?: string | null;
|
|
93
|
+
/**
|
|
94
|
+
* How to translate into each language, keyed by language.
|
|
95
|
+
*
|
|
96
|
+
* Per language rather than one field, because translation rules are per
|
|
97
|
+
* language: bokmål or nynorsk, `du` or `De`, which product names stay in
|
|
98
|
+
* English. Only the target language's note is sent, so a French rule does not
|
|
99
|
+
* ride along in a Norwegian request.
|
|
100
|
+
*
|
|
101
|
+
* Keyed by language, so it holds every one of them once it exists at all —
|
|
102
|
+
* `null` where a language needs no special instruction, which is most of them.
|
|
103
|
+
* That is the same rule every locale-keyed record follows (see
|
|
104
|
+
* `declaredKeys.ts`): a gap you can count and see in a diff, rather than a key
|
|
105
|
+
* that is simply not there. A project with no notes at all leaves the whole
|
|
106
|
+
* field unset and nothing is required.
|
|
107
|
+
*/
|
|
108
|
+
translation?: Record<string, string | null> | null;
|
|
58
109
|
};
|
|
59
110
|
/**
|
|
60
111
|
* The cap on each of the assistant's prose fields, in characters.
|