@aiquants/spreadsheet 0.12.0 → 0.13.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.
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @fileoverview Built-in data-validation verdict messages per UI chrome locale — the table that
3
+ * validateCellText reads when a rule carries no `errorMessage`. INTERNAL: not exported from the
4
+ * package barrel; the public override stays the per-rule `errorMessage`. Pure, React-free.
5
+ *
6
+ * Contract (pinned by validationMessages.spec.ts and dataValidation.spec.ts):
7
+ * - One frozen ValidationMessageCatalog per SpreadsheetLocale; every template takes the bounds
8
+ * already normalised by dataValidation.ts (number / date bounds trimmed, textLength bounds
9
+ * stringified, checkbox strings defaulted) and never re-normalises them.
10
+ * - Templates are WHOLE SENTENCES per (rule type, op): Japanese puts the bound before the
11
+ * predicate, so a fragment table ("greater than" + bound) cannot express it.
12
+ * - `en` is byte-identical to the pre-i18n English summaries ("Value must be a number greater
13
+ * than 5." etc.); `ja` follows the workspace Japanese spacing rule (a half-width space around
14
+ * ASCII alphanumerics, 「」 exempt).
15
+ * - between / notBetween receive `b` only for complete rules: an incomplete rule is inert and
16
+ * never produces a message.
17
+ *
18
+ * UI クロームロケール別の組込みデータ検証メッセージ — 規則に `errorMessage` が無いとき
19
+ * validateCellText が読む表。内部専用 (パッケージの barrel からは export しない — 公開の上書き手段は
20
+ * 規則毎の `errorMessage`)。純関数・React 非依存。
21
+ * 契約: SpreadsheetLocale 毎に凍結した ValidationMessageCatalog を 1 つ持ち、各テンプレートは
22
+ * dataValidation.ts が正規化済みの境界 (number / date は trim、textLength は文字列化、checkbox は既定
23
+ * 適用済み) を受け取り再正規化しない。テンプレートは (規則型, op) 毎の文全体 (日本語は境界が述語より
24
+ * 前に来るため、断片表では表現不能)。`en` は i18n 以前の英語要約とバイト単位で同一、`ja` は
25
+ * ワークスペースの和文スペース規則 (半角英数字の前後に半角スペース、「」内は対象外) に従う。
26
+ * between / notBetween の `b` は完全な規則でだけ渡る (不完全な規則は不活性でメッセージを作らない)。
27
+ */
28
+
29
+ import type { ValidationCompareOp, ValidationTextOp } from "./dataValidation.ts"
30
+ import type { SpreadsheetLocale } from "./labels.ts"
31
+
32
+ /**
33
+ * Whole-sentence templates of one ordered rule family (number / textLength / date), one per
34
+ * comparison op; `b` is the upper bound for between / notBetween and unused by the other six.
35
+ * 順序規則族 (number / textLength / date) 1 つ分の文全体テンプレート (比較 op 毎に 1 つ)。`b` は
36
+ * between / notBetween の上限で、他の 6 op では未使用。
37
+ */
38
+ type CompareTemplates = { readonly [K in ValidationCompareOp]: (a: string, b: string | undefined) => string }
39
+
40
+ /**
41
+ * The built-in verdict messages of one locale: a sentence for `list`, per-op templates for the
42
+ * three ordered families, per-op templates for `text` (the four needle ops take the needle; the
43
+ * two shape checks are fixed sentences), and templates for `checkbox` and `custom`.
44
+ * 1 ロケール分の組込み判定メッセージ。list の文、順序 3 族の op 別テンプレート、text の op 別
45
+ * テンプレート (needle 系 4 op は探索文字列を受け取り、形式検査 2 op は固定文)、checkbox と custom の
46
+ * テンプレート。
47
+ */
48
+ export type ValidationMessageCatalog = {
49
+ readonly list: string
50
+ readonly number: CompareTemplates
51
+ readonly textLength: CompareTemplates
52
+ readonly date: CompareTemplates
53
+ readonly text: {
54
+ readonly contains: (v: string) => string
55
+ readonly notContains: (v: string) => string
56
+ readonly startsWith: (v: string) => string
57
+ readonly endsWith: (v: string) => string
58
+ readonly isEmail: string
59
+ readonly isUrl: string
60
+ }
61
+ readonly checkbox: (checked: string, unchecked: string) => string
62
+ readonly custom: (formula: string) => string
63
+ }
64
+
65
+ /** Compile-time proof that the `text` templates are keyed by exactly the ValidationTextOp vocabulary (a new op without a message, or a stray key, turns tsc red). / text テンプレートのキーが ValidationTextOp 語彙と完全一致することの型水準の証明 (メッセージの無い新 op や余分なキーは tsc が赤)。 */
66
+ const textTemplatesMatchTextOps: [keyof ValidationMessageCatalog["text"]] extends [ValidationTextOp] ? ([ValidationTextOp] extends [keyof ValidationMessageCatalog["text"]] ? true : never) : never = true
67
+ void textTemplatesMatchTextOps
68
+
69
+ /** English wording of the four ordered ops of one family (the other four are shared). / 規則族 1 つ分の順序 4 op の英語の言い回し (残る 4 つは共通)。 */
70
+ type OrderWords = { readonly gt: string; readonly gte: string; readonly lt: string; readonly lte: string }
71
+
72
+ /** English wording for number / textLength messages. / number / textLength メッセージの英語の言い回し。 */
73
+ const NUMERIC_WORDS: OrderWords = { gt: "greater than", gte: "greater than or equal to", lt: "less than", lte: "less than or equal to" }
74
+
75
+ /** English wording for date messages. / date メッセージの英語の言い回し。 */
76
+ const DATE_WORDS: OrderWords = { gt: "after", gte: "on or after", lt: "before", lte: "on or before" }
77
+
78
+ /**
79
+ * English phrase for an ordered op with the family wording: "greater than 5", "between 1 and 9", ...
80
+ * 規則族の語彙を使った順序 op の英語句 ("greater than 5"、"between 1 and 9" など)。
81
+ *
82
+ * @param op - The comparison op / 比較 op
83
+ * @param a - The normalised lower (or only) bound / 正規化済みの下限 (または唯一の境界)
84
+ * @param b - The normalised upper bound for between / notBetween / between 系の正規化済み上限
85
+ * @param words - The family wording of the four ordered ops / 順序 4 op の規則族別の言い回し
86
+ * @returns The comparison phrase without subject or full stop / 主語と句点を含まない比較句
87
+ */
88
+ const comparePhrase = (op: ValidationCompareOp, a: string, b: string | undefined, words: OrderWords): string => {
89
+ switch (op) {
90
+ case "gt":
91
+ case "gte":
92
+ case "lt":
93
+ case "lte":
94
+ return `${words[op]} ${a}`
95
+ case "eq":
96
+ return `equal to ${a}`
97
+ case "neq":
98
+ return `not equal to ${a}`
99
+ case "between":
100
+ return `between ${a} and ${b}`
101
+ default:
102
+ // 残る順序 op は notBetween (語彙外の op は dataValidation.ts の assertRuleShape が先に拒否済み)
103
+ return `not between ${a} and ${b}`
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Builds the English templates of one ordered family as `${subject} ${phrase}.`, e.g.
109
+ * "Value must be a number greater than 5.".
110
+ * 順序規則族 1 つ分の英語テンプレートを `${subject} ${phrase}.` の形で構築 (例:
111
+ * "Value must be a number greater than 5.")。
112
+ *
113
+ * @param subject - The sentence head before the comparison phrase / 比較句の前に置く文頭
114
+ * @param words - The family wording of the four ordered ops / 順序 4 op の規則族別の言い回し
115
+ * @returns The frozen per-op templates / op 別の凍結テンプレート
116
+ */
117
+ const englishCompareTemplates = (subject: string, words: OrderWords): CompareTemplates => {
118
+ const sentence =
119
+ (op: ValidationCompareOp) =>
120
+ (a: string, b: string | undefined): string =>
121
+ `${subject} ${comparePhrase(op, a, b, words)}.`
122
+ return Object.freeze({
123
+ gt: sentence("gt"),
124
+ gte: sentence("gte"),
125
+ lt: sentence("lt"),
126
+ lte: sentence("lte"),
127
+ eq: sentence("eq"),
128
+ neq: sentence("neq"),
129
+ between: sentence("between"),
130
+ notBetween: sentence("notBetween"),
131
+ })
132
+ }
133
+
134
+ /** The English catalog — byte-identical to the pre-i18n built-in summaries. / 英語カタログ — i18n 以前の組込み要約とバイト単位で同一。 */
135
+ const EN: ValidationMessageCatalog = Object.freeze({
136
+ list: "Value must be one of the list items.",
137
+ number: englishCompareTemplates("Value must be a number", NUMERIC_WORDS),
138
+ textLength: englishCompareTemplates("Text length must be", NUMERIC_WORDS),
139
+ date: englishCompareTemplates("Value must be a date", DATE_WORDS),
140
+ text: Object.freeze({
141
+ contains: (v: string) => `Value must contain "${v}".`,
142
+ notContains: (v: string) => `Value must not contain "${v}".`,
143
+ startsWith: (v: string) => `Value must start with "${v}".`,
144
+ endsWith: (v: string) => `Value must end with "${v}".`,
145
+ isEmail: "Value must be a valid email address.",
146
+ isUrl: "Value must be a valid URL.",
147
+ }),
148
+ checkbox: (checked: string, unchecked: string) => `Value must be "${checked}" or "${unchecked}".`,
149
+ custom: (formula: string) => `Value does not satisfy the formula ${formula}.`,
150
+ })
151
+
152
+ /** The Japanese catalog — whole sentences with the bound first. / 日本語カタログ — 境界を先に置いた文全体。 */
153
+ const JA: ValidationMessageCatalog = Object.freeze({
154
+ list: "リストの項目のいずれかを入力してください。",
155
+ number: Object.freeze({
156
+ gt: (a: string) => `${a} より大きい数値を入力してください。`,
157
+ gte: (a: string) => `${a} 以上の数値を入力してください。`,
158
+ lt: (a: string) => `${a} より小さい数値を入力してください。`,
159
+ lte: (a: string) => `${a} 以下の数値を入力してください。`,
160
+ eq: (a: string) => `${a} に等しい数値を入力してください。`,
161
+ neq: (a: string) => `${a} 以外の数値を入力してください。`,
162
+ between: (a: string, b: string | undefined) => `${a} から ${b} までの数値を入力してください。`,
163
+ notBetween: (a: string, b: string | undefined) => `${a} から ${b} までの範囲外の数値を入力してください。`,
164
+ }),
165
+ textLength: Object.freeze({
166
+ gt: (a: string) => `${a} 文字より長いテキストを入力してください。`,
167
+ gte: (a: string) => `${a} 文字以上のテキストを入力してください。`,
168
+ lt: (a: string) => `${a} 文字より短いテキストを入力してください。`,
169
+ lte: (a: string) => `${a} 文字以下のテキストを入力してください。`,
170
+ eq: (a: string) => `${a} 文字のテキストを入力してください。`,
171
+ neq: (a: string) => `${a} 文字以外の長さのテキストを入力してください。`,
172
+ between: (a: string, b: string | undefined) => `${a} 文字から ${b} 文字までのテキストを入力してください。`,
173
+ notBetween: (a: string, b: string | undefined) => `${a} 文字から ${b} 文字までの範囲外の長さのテキストを入力してください。`,
174
+ }),
175
+ date: Object.freeze({
176
+ gt: (a: string) => `${a} より後の日付を入力してください。`,
177
+ gte: (a: string) => `${a} 以降の日付を入力してください。`,
178
+ lt: (a: string) => `${a} より前の日付を入力してください。`,
179
+ lte: (a: string) => `${a} 以前の日付を入力してください。`,
180
+ eq: (a: string) => `${a} の日付を入力してください。`,
181
+ neq: (a: string) => `${a} 以外の日付を入力してください。`,
182
+ between: (a: string, b: string | undefined) => `${a} から ${b} までの日付を入力してください。`,
183
+ notBetween: (a: string, b: string | undefined) => `${a} から ${b} までの範囲外の日付を入力してください。`,
184
+ }),
185
+ text: Object.freeze({
186
+ contains: (v: string) => `「${v}」を含む値を入力してください。`,
187
+ notContains: (v: string) => `「${v}」を含まない値を入力してください。`,
188
+ startsWith: (v: string) => `「${v}」で始まる値を入力してください。`,
189
+ endsWith: (v: string) => `「${v}」で終わる値を入力してください。`,
190
+ isEmail: "有効なメールアドレスを入力してください。",
191
+ isUrl: "有効な URL を入力してください。",
192
+ }),
193
+ checkbox: (checked: string, unchecked: string) => `「${checked}」または「${unchecked}」を入力してください。`,
194
+ custom: (formula: string) => `数式 ${formula} を満たす値を入力してください。`,
195
+ })
196
+
197
+ /**
198
+ * The built-in verdict messages per UI chrome locale (frozen at every level). Keyed by exactly the
199
+ * SpreadsheetLocale set, so validateCellText checks a locale by own-key membership here.
200
+ * UI クロームロケール別の組込み判定メッセージ (全階層で凍結)。キーは SpreadsheetLocale 集合と完全一致
201
+ * するため、validateCellText はここへの自身のキーの所属でロケールを検査する。
202
+ */
203
+ export const VALIDATION_MESSAGE_CATALOGS: Readonly<Record<SpreadsheetLocale, ValidationMessageCatalog>> = Object.freeze({ en: EN, ja: JA })