@eifi1/ui-kit 0.27.1 → 0.28.1
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/README.md +7 -5
- package/dist/chart.d.ts +1 -2
- package/dist/components/amount-input.d.ts +1 -2
- package/dist/components/button-group.d.ts +1 -2
- package/dist/components/calculator.d.ts +1 -2
- package/dist/components/confirm-dialog.d.ts +1 -2
- package/dist/components/copy-button.d.ts +1 -2
- package/dist/components/danger-confirm.d.ts +1 -2
- package/dist/components/facing-pair.d.ts +1 -2
- package/dist/components/file-button.d.ts +1 -2
- package/dist/components/file-dropzone.d.ts +1 -2
- package/dist/components/form-actions.d.ts +1 -2
- package/dist/components/iban-input.d.ts +1 -2
- package/dist/components/language-select.d.ts +1 -2
- package/dist/components/legal-page.d.ts +267 -0
- package/dist/components/legal-page.js +272 -0
- package/dist/components/legal-page.js.map +1 -0
- package/dist/components/legal.d.ts +70 -87
- package/dist/components/legal.js +232 -11
- package/dist/components/legal.js.map +1 -1
- package/dist/components/money-field.d.ts +1 -2
- package/dist/components/number-field.d.ts +1 -2
- package/dist/components/number-input.d.ts +1 -2
- package/dist/components/numpad-sheet.d.ts +1 -2
- package/dist/components/phone-input.d.ts +1 -2
- package/dist/components/series-chart.d.ts +1 -2
- package/dist/components/settings-fields.d.ts +1 -2
- package/dist/components/share-card.d.ts +1 -2
- package/dist/components/text-link.d.ts +1 -2
- package/dist/components/time-input.d.ts +1 -2
- package/dist/components/translation-review.d.ts +2 -1
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui.d.ts +1 -2
- package/dist/feedback/feedback-attachment.d.ts +1 -2
- package/dist/feedback/feedback-context.d.ts +1 -2
- package/dist/feedback/feedback-crash.d.ts +1 -2
- package/dist/feedback/feedback-dialog.d.ts +1 -2
- package/dist/feedback/feedback-inbox.d.ts +1 -2
- package/dist/feedback/feedback-labels.d.ts +1 -2
- package/dist/feedback/feedback-menu.d.ts +1 -2
- package/dist/feedback/feedback-record.d.ts +1 -2
- package/dist/feedback/feedback-row-detail.d.ts +1 -2
- package/dist/feedback/feedback-status-undo.d.ts +1 -2
- package/dist/feedback/feedback-submit.d.ts +1 -2
- package/dist/feedback/feedback-swipe.d.ts +1 -2
- package/dist/feedback/feedback-table.d.ts +1 -2
- package/dist/feedback/feedback-thread.d.ts +1 -2
- package/dist/feedback.d.ts +1 -2
- package/dist/hooks/use-file-drop.d.ts +1 -2
- package/dist/hooks/use-noindex.d.ts +26 -0
- package/dist/hooks/use-noindex.js +18 -0
- package/dist/hooks/use-noindex.js.map +1 -0
- package/dist/i18n/defaults.d.ts +1 -2
- package/dist/i18n/german.d.ts +1 -2
- package/dist/i18n/german.js +95 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +1 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +1 -2
- package/dist/i18n/locales/de-CH.d.ts +1 -2
- package/dist/i18n/locales/en.d.ts +1 -2
- package/dist/i18n/locales/es.d.ts +1 -2
- package/dist/i18n/locales/es.js +95 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +1 -2
- package/dist/i18n/locales/fr.js +97 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +1 -2
- package/dist/i18n/locales/hu.js +98 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +1 -2
- package/dist/i18n/locales/it.js +98 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +1 -2
- package/dist/i18n/locales/zh.js +95 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +24 -5
- package/dist/i18n/review.js +26 -3
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-DQg82eHB.d.ts → kit-labels-Du0uHBq4.d.ts} +342 -4
- package/dist/lib/translation-review.d.ts +26 -3
- package/dist/lib/translation-review.js +10 -2
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/rhf/fields.d.ts +1 -2
- package/dist/rhf/form.d.ts +1 -2
- package/dist/rhf.d.ts +1 -2
- package/dist/shell/app-shell.d.ts +1 -2
- package/dist/shell/top-bar-brand.d.ts +1 -2
- package/dist/shell.d.ts +1 -2
- package/dist/wizard/stepper-nav.d.ts +1 -2
- package/dist/wizard.d.ts +1 -2
- package/package.json +1 -1
- package/src/components/legal-page.tsx +601 -0
- package/src/components/legal.tsx +540 -46
- package/src/components/translation-review.tsx +2 -1
- package/src/hooks/use-noindex.ts +37 -0
- package/src/i18n/german.ts +91 -0
- package/src/i18n/kit-labels.tsx +4 -1
- package/src/i18n/locales/es.ts +91 -0
- package/src/i18n/locales/fr.ts +93 -0
- package/src/i18n/locales/hu.ts +94 -0
- package/src/i18n/locales/it.ts +94 -0
- package/src/i18n/locales/zh.ts +91 -0
- package/src/i18n/review.ts +57 -3
- package/src/index.ts +41 -2
- package/src/lib/translation-review.ts +43 -4
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -720,8 +720,99 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
720
720
|
waking: (appName) =>
|
|
721
721
|
`无人使用${appName ? ` ${appName} ` : "本应用"}时,服务器会进入休眠,因此暂停后的第一个请求需要重新启动它。这可能需要一点时间——不会丢失任何内容,页面会自动加载完成。`,
|
|
722
722
|
},
|
|
723
|
+
// 0.28.0 — docs/legal-harmonization.md §4.3/§4.4: Kurvenschmiede's reviewed Chinese,
|
|
724
|
+
// brought into line with the kit's English; it goes back to Kurvenschmiede for review.
|
|
723
725
|
legal: {
|
|
724
726
|
navLabel: "法律信息",
|
|
727
|
+
links: {
|
|
728
|
+
impressum: "运营者信息",
|
|
729
|
+
privacy: "隐私政策",
|
|
730
|
+
terms: "使用条款",
|
|
731
|
+
},
|
|
732
|
+
titles: {
|
|
733
|
+
impressum: "运营者信息",
|
|
734
|
+
privacy: "隐私政策",
|
|
735
|
+
terms: "使用条款",
|
|
736
|
+
},
|
|
737
|
+
backHome: "返回首页",
|
|
738
|
+
// Book-title marks around the two documents, outside the links.
|
|
739
|
+
accept: "我已阅读并同意《{terms}》和《{privacy}》",
|
|
740
|
+
notice: {
|
|
741
|
+
beta: "封闭测试阶段。这些文本尚未经律师审阅,将在公开发布之前完成审阅。",
|
|
742
|
+
privacy:
|
|
743
|
+
"封闭测试阶段。以下法律措辞尚未经律师审阅,将在公开发布之前完成审阅。其中的技术说明——存储了什么、存储在哪里以及谁可以读取——描述的是软件目前的实际行为,旨在供人对照软件加以核实。",
|
|
744
|
+
},
|
|
745
|
+
translation: {
|
|
746
|
+
note: (o) =>
|
|
747
|
+
`本页为方便阅读而提供的译文,以${o.bindingLanguage}版本为准。`,
|
|
748
|
+
show: (o) => `查看${o.bindingLanguage}版本`,
|
|
749
|
+
},
|
|
750
|
+
sections: {
|
|
751
|
+
impressum: {
|
|
752
|
+
operator: {
|
|
753
|
+
title: "运营者",
|
|
754
|
+
body: (o) =>
|
|
755
|
+
`${o.name}\n${o.postalCode} ${o.city}\n${o.country}\n\n完整的邮寄地址可应具有正当法律利益者的要求提供;请发送邮件至下方联系地址。`,
|
|
756
|
+
},
|
|
757
|
+
contact: {
|
|
758
|
+
title: "联系方式",
|
|
759
|
+
body: (o) => `电子邮件:${o.email}`,
|
|
760
|
+
},
|
|
761
|
+
disclaimer: {
|
|
762
|
+
title: "内容与链接的责任",
|
|
763
|
+
body: "这是一个私人的非商业项目,在封闭测试期间提供,不作任何保证。我们所链接的外部网站由其各自的运营者负责;我们无法控制其内容。",
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
privacy: {
|
|
767
|
+
controller: {
|
|
768
|
+
title: "数据控制者",
|
|
769
|
+
body: (o) =>
|
|
770
|
+
`负责处理本服务中个人数据的是:\n${o.name}\n${o.postalCode} ${o.city},${o.country}\n电子邮件:${o.email}\n\n完整的邮寄地址可应数据主体和监管机构的要求提供。`,
|
|
771
|
+
},
|
|
772
|
+
legal_basis: {
|
|
773
|
+
title: "法律依据",
|
|
774
|
+
body: "由于运营者位于瑞士,数据处理受瑞士《联邦数据保护法》(FADP)约束。如果欧盟《通用数据保护条例》(GDPR)适用于您,我们的依据是为提供服务而履行合同(GDPR 第 6 条第 1 款 b 项)以及我们在运行和保护服务方面的合法利益(GDPR 第 6 条第 1 款 f 项)。",
|
|
775
|
+
},
|
|
776
|
+
browser: {
|
|
777
|
+
title: "您的浏览器存储什么",
|
|
778
|
+
lead: "不使用 Cookie。应用程序会在您浏览器的存储中保留以下内容:",
|
|
779
|
+
tail: "这些内容都不会用于跟踪您。等待发送的条目会尽快发送给我们;其他所有内容都保留在您的设备上。清除网站数据后,这些内容会全部消失;退出登录会删除登录令牌。",
|
|
780
|
+
},
|
|
781
|
+
rights: {
|
|
782
|
+
title: "您的权利",
|
|
783
|
+
lead: "您享有访问、更正、删除、限制处理、数据可携和反对的权利。",
|
|
784
|
+
tail: "如果您在瑞士,可以联系联邦数据保护和信息专员(FDPIC);如果您在欧盟,可以向您当地的监管机构投诉。",
|
|
785
|
+
},
|
|
786
|
+
contact: {
|
|
787
|
+
title: "数据保护联系方式",
|
|
788
|
+
body: (o) => `如有任何隐私方面的请求,请联系:${o.email}`,
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
terms: {
|
|
792
|
+
warranty: {
|
|
793
|
+
title: "不作保证",
|
|
794
|
+
body: "在法律允许的范围内,本服务按“现状”和“现有”提供,不作任何形式的保证。作为测试版,它可能包含错误、发生变化或随时中断——请自行备份重要数据。",
|
|
795
|
+
},
|
|
796
|
+
liability: {
|
|
797
|
+
title: "责任限制",
|
|
798
|
+
body: "在适用法律允许的范围内,运营者对因使用或无法使用本服务而产生的任何间接损失或后果性损失不承担责任。本条款不限制依法不得限制的责任。",
|
|
799
|
+
},
|
|
800
|
+
changes: {
|
|
801
|
+
title: "条款变更",
|
|
802
|
+
body: "本条款可能会随着服务的发展而更新。重大变更将通过电子邮件或在应用程序中公布;变更后继续使用本服务即表示您接受变更。",
|
|
803
|
+
},
|
|
804
|
+
language: {
|
|
805
|
+
title: "语言",
|
|
806
|
+
body: (o) =>
|
|
807
|
+
`本条款以${o.bindingLanguage}撰写。其他语言的译文仅供参考;如译文与原文不一致,以${o.bindingLanguage}版本为准。`,
|
|
808
|
+
},
|
|
809
|
+
law: {
|
|
810
|
+
title: "适用法律",
|
|
811
|
+
body: (o) =>
|
|
812
|
+
`本条款受瑞士法律管辖,但不适用其冲突法规则。在法律允许的范围内,管辖法院所在地为${o.country} ${o.city}(${o.region})。`,
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
},
|
|
725
816
|
},
|
|
726
817
|
characterCount: {
|
|
727
818
|
count: (used, max) => `已输入 ${n(used)}/${n(max)} 个字符`,
|
package/src/i18n/review.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { legalOperatorText } from "../components/legal";
|
|
2
|
+
import type { LegalOperator, LegalOperatorText } from "../components/legal";
|
|
1
3
|
import type { UiKitLabels } from "./kit-labels";
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -6,7 +8,7 @@ import type { UiKitLabels } from "./kit-labels";
|
|
|
6
8
|
* keksdose reviews its own locale bundles on its /translations page — one verdict per
|
|
7
9
|
* locale and key, stored with the text the reviewer saw, so a later change re-opens it.
|
|
8
10
|
* The kit's words appear on the same screens but live in TypeScript, not in the app's
|
|
9
|
-
* bundles, and
|
|
11
|
+
* bundles, and 143 of them are functions (`(count) => "3 results"`; 0.28.0, every one
|
|
10
12
|
* listed in {@link KIT_LABEL_SAMPLES}), which a bundle flattener skips. This turns a
|
|
11
13
|
* label tree into rows that page can join on.
|
|
12
14
|
*
|
|
@@ -18,6 +20,13 @@ import type { UiKitLabels } from "./kit-labels";
|
|
|
18
20
|
* where the wording may depend on them — 1 and 3 — so a reviewer sees singular and
|
|
19
21
|
* plural. The key names the sample: `combobox.resultCount(1)`,
|
|
20
22
|
* `shareCard.removeConfirm(name)`, `serverWake.waking()`.
|
|
23
|
+
* - The legal sections that name the operator (0.28) take one object; its sample is a
|
|
24
|
+
* placeholder per field and the key calls it `operator`:
|
|
25
|
+
* `legal.sections.privacy.controller.body(operator)`. Pass the app's operator in
|
|
26
|
+
* `options` and those rows read the real sentence — same keys — so the lawyer
|
|
27
|
+
* reviewing the `legal` area reads what the page says:
|
|
28
|
+
*
|
|
29
|
+
* kitLabelStrings(UI_KIT_LABELS_DE_CH, { operator: OPERATOR, locale: "de-CH" })
|
|
21
30
|
*
|
|
22
31
|
* Keys depend only on the samples, never on the locale, so the rows of `de-CH` and of the
|
|
23
32
|
* English reference line up key by key. Pass exactly the tree the app hands
|
|
@@ -29,16 +38,20 @@ import type { UiKitLabels } from "./kit-labels";
|
|
|
29
38
|
* A label that throws on its sample becomes a row starting with "⚠", so one broken
|
|
30
39
|
* override shows up on the page instead of taking the page down.
|
|
31
40
|
*/
|
|
32
|
-
export function kitLabelStrings(labels: UiKitLabels): Record<string, string> {
|
|
41
|
+
export function kitLabelStrings(labels: UiKitLabels, options: KitLabelStringsOptions = {}): Record<string, string> {
|
|
33
42
|
const out: Record<string, string> = {};
|
|
43
|
+
const operator = options.operator && legalOperatorText(options.operator, options.locale);
|
|
34
44
|
const walk = (node: unknown, path: string) => {
|
|
35
45
|
if (typeof node === "string") {
|
|
36
46
|
out[path] = node;
|
|
37
47
|
} else if (typeof node === "function") {
|
|
38
48
|
for (const args of KIT_LABEL_SAMPLES[path] ?? [[]]) {
|
|
49
|
+
// The key is the SAMPLE's (`…body(operator)`), whatever the label is called with,
|
|
50
|
+
// so rows with and without the app's operator join on the same keys.
|
|
51
|
+
const called = operator ? args.map((arg) => (arg === LEGAL_OPERATOR_SAMPLE ? operator : arg)) : args;
|
|
39
52
|
out[`${path}(${args.map(sampleName).filter((a) => a !== "").join(", ")})`] = render(
|
|
40
53
|
node as (...args: unknown[]) => unknown,
|
|
41
|
-
|
|
54
|
+
called,
|
|
42
55
|
);
|
|
43
56
|
}
|
|
44
57
|
} else if (node !== null && typeof node === "object") {
|
|
@@ -49,6 +62,20 @@ export function kitLabelStrings(labels: UiKitLabels): Record<string, string> {
|
|
|
49
62
|
return out;
|
|
50
63
|
}
|
|
51
64
|
|
|
65
|
+
export interface KitLabelStringsOptions {
|
|
66
|
+
/**
|
|
67
|
+
* The app's operator (0.28): the legal sections that name it — the imprint's operator
|
|
68
|
+
* and contact, the controller, the privacy contact, the governing law — are rendered
|
|
69
|
+
* with the real values instead of `{{name}}`-style placeholders, so a reviewer (the
|
|
70
|
+
* lawyer, under the `legal` area) reads the sentence the page shows. The keys do not
|
|
71
|
+
* change: `legal.sections.impressum.operator.body(operator)` either way.
|
|
72
|
+
*/
|
|
73
|
+
operator?: LegalOperator;
|
|
74
|
+
/** The language the operator's country is named in — the locale these rows are
|
|
75
|
+
* for ("de-CH" → "Schweiz"). Default English. */
|
|
76
|
+
locale?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
52
79
|
function render(label: (...args: unknown[]) => unknown, args: readonly unknown[]): string {
|
|
53
80
|
try {
|
|
54
81
|
return String(label(...args));
|
|
@@ -61,12 +88,30 @@ function render(label: (...args: unknown[]) => unknown, args: readonly unknown[]
|
|
|
61
88
|
* itself, an empty string as `""`, a missing optional argument not at all. */
|
|
62
89
|
function sampleName(arg: unknown): string {
|
|
63
90
|
if (arg === undefined) return "";
|
|
91
|
+
if (arg === LEGAL_OPERATOR_SAMPLE) return "operator";
|
|
64
92
|
if (typeof arg === "string") return /^\{\{(\w+)\}\}$/.exec(arg)?.[1] ?? JSON.stringify(arg);
|
|
65
93
|
return String(arg);
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
const one = [[1], [3]] as const;
|
|
69
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The legal sections' operator (0.28) as placeholders, one per field — so a translation
|
|
100
|
+
* that drops the city or the email is caught by the same check as a dropped `{{name}}`
|
|
101
|
+
* anywhere else. Named `operator` in the key; {@link kitLabelStrings}' `operator` option
|
|
102
|
+
* swaps the app's real values in for it.
|
|
103
|
+
*/
|
|
104
|
+
const LEGAL_OPERATOR_SAMPLE: LegalOperatorText = Object.freeze({
|
|
105
|
+
name: "{{name}}",
|
|
106
|
+
postalCode: "{{postalCode}}",
|
|
107
|
+
city: "{{city}}",
|
|
108
|
+
region: "{{region}}",
|
|
109
|
+
country: "{{country}}",
|
|
110
|
+
email: "{{email}}",
|
|
111
|
+
bindingLanguage: "{{bindingLanguage}}",
|
|
112
|
+
});
|
|
113
|
+
const operatorSample = [[LEGAL_OPERATOR_SAMPLE]] as const;
|
|
114
|
+
|
|
70
115
|
/**
|
|
71
116
|
* The sample arguments of every function label in {@link UiKitLabels}, by dot path.
|
|
72
117
|
* `src/i18n/__tests__/review.test.ts` fails when a function label has no entry here, so
|
|
@@ -240,4 +285,13 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
|
|
|
240
285
|
],
|
|
241
286
|
"columnMapper.oneOf": [["{{roles}}"]],
|
|
242
287
|
"columnMapper.missing": [["{{roles}}"]],
|
|
288
|
+
"legal.sections.impressum.operator.body": operatorSample,
|
|
289
|
+
"legal.sections.impressum.contact.body": operatorSample,
|
|
290
|
+
"legal.sections.privacy.controller.body": operatorSample,
|
|
291
|
+
"legal.sections.privacy.contact.body": operatorSample,
|
|
292
|
+
"legal.sections.terms.law.body": operatorSample,
|
|
293
|
+
// 0.28.1: the binding language, named in the reader's language.
|
|
294
|
+
"legal.sections.terms.language.body": operatorSample,
|
|
295
|
+
"legal.translation.note": operatorSample,
|
|
296
|
+
"legal.translation.show": operatorSample,
|
|
243
297
|
};
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ export type { UseAnnounceReturn, UseAnnounceOptions, AnnounceRegionProps } from
|
|
|
36
36
|
export { useOverlayHistory } from "./hooks/use-overlay-history";
|
|
37
37
|
export { useCloseTransition, OVERLAY_EXIT_MS } from "./hooks/use-close-transition";
|
|
38
38
|
export { useRowSwipe } from "./hooks/use-row-swipe";
|
|
39
|
+
// 0.28: keksdose's noindex marker, for the legal and auth pages of every app.
|
|
40
|
+
export { useNoIndex } from "./hooks/use-noindex";
|
|
39
41
|
export type { SwipeStage, RowSwipeOptions, RowSwipeReturn } from "./hooks/use-row-swipe";
|
|
40
42
|
// Any element as a file drop target, screened like FileButton (kastlan).
|
|
41
43
|
export { useFileDrop, dragHasFiles } from "./hooks/use-file-drop";
|
|
@@ -379,6 +381,7 @@ export * from "./i18n/kit-labels";
|
|
|
379
381
|
export * from "./i18n/defaults";
|
|
380
382
|
// The kit's words as `key → text` rows for an app's translation review (keksdose).
|
|
381
383
|
export { kitLabelStrings } from "./i18n/review";
|
|
384
|
+
export type { KitLabelStringsOptions } from "./i18n/review";
|
|
382
385
|
// The seven languages, how an app resolves one, and the kit catalogue loader for it.
|
|
383
386
|
export * from "./i18n/languages";
|
|
384
387
|
// CLDR plural categories for an app's i18next catalogue, filled from `_other`.
|
|
@@ -396,6 +399,8 @@ export {
|
|
|
396
399
|
reviewStatus,
|
|
397
400
|
keyInArea,
|
|
398
401
|
keyInAreas,
|
|
402
|
+
// 0.28: the area a key is grouped under — `kit.legal.*` in `legal` (legal round §7.5).
|
|
403
|
+
reviewAreaOf,
|
|
399
404
|
translationRows,
|
|
400
405
|
summariseRows,
|
|
401
406
|
DEFAULT_TRANSLATION_REVIEW_FILTER,
|
|
@@ -455,8 +460,42 @@ export type {
|
|
|
455
460
|
TranslationReviewGroupBy,
|
|
456
461
|
} from "./components/translation-review";
|
|
457
462
|
// The legal pages' shell (0.19, H10): Imprint, Privacy Policy, Terms in every app.
|
|
458
|
-
|
|
459
|
-
|
|
463
|
+
// 0.28 (docs/legal-harmonization.md): the shared wording, the page frame, the kit's
|
|
464
|
+
// sections, the public footer and the terms checkbox.
|
|
465
|
+
export {
|
|
466
|
+
LegalLayout,
|
|
467
|
+
LegalSection,
|
|
468
|
+
LegalLinks,
|
|
469
|
+
LegalFooter,
|
|
470
|
+
DEFAULT_LEGAL_LABELS,
|
|
471
|
+
LEGAL_HREFS,
|
|
472
|
+
legalOperatorText,
|
|
473
|
+
useLegalLabels,
|
|
474
|
+
} from "./components/legal";
|
|
475
|
+
export type {
|
|
476
|
+
LegalLabels,
|
|
477
|
+
LegalLink,
|
|
478
|
+
LegalLinksProps,
|
|
479
|
+
LegalSectionProps,
|
|
480
|
+
LegalLayoutProps,
|
|
481
|
+
LegalFooterProps,
|
|
482
|
+
LegalOperator,
|
|
483
|
+
LegalOperatorText,
|
|
484
|
+
LegalPageKey,
|
|
485
|
+
LegalTextSectionLabels,
|
|
486
|
+
LegalOperatorSectionLabels,
|
|
487
|
+
LegalFramedSectionLabels,
|
|
488
|
+
} from "./components/legal";
|
|
489
|
+
export { LegalPage, LegalKitSection, LegalAcceptCheckbox, LEGAL_SKELETON } from "./components/legal-page";
|
|
490
|
+
export type {
|
|
491
|
+
LegalPageProps,
|
|
492
|
+
LegalKitSectionProps,
|
|
493
|
+
LegalKitSectionKey,
|
|
494
|
+
LegalAcceptCheckboxProps,
|
|
495
|
+
LegalSkeletonEntry,
|
|
496
|
+
LegalSkeletonPage,
|
|
497
|
+
LegalSectionOwner,
|
|
498
|
+
} from "./components/legal-page";
|
|
460
499
|
|
|
461
500
|
// ── 0.22.0: the inputs round (kastlan's, keksdose's and Kurvenschmiede's audits) ──
|
|
462
501
|
export * from "./components/checkbox-group";
|
|
@@ -210,7 +210,10 @@ export function placeholderTokens(text: string): string[] {
|
|
|
210
210
|
const found: string[] = [];
|
|
211
211
|
for (const m of text.matchAll(PLACEHOLDER_RE)) {
|
|
212
212
|
if (m[1]) found.push(`{{${m[1]}}}`);
|
|
213
|
-
|
|
213
|
+
// `{ref:retention}` (ui-kit 0.28's legal cross-reference) keeps its target: a
|
|
214
|
+
// translation that points at another section changed what the sentence says. Every
|
|
215
|
+
// other `{x:spec}` is a format spec, which a translation may change.
|
|
216
|
+
else if (m[2]) found.push(m[2] === "ref" ? m[0] : `{${m[2]}}`);
|
|
214
217
|
else if (m[3]) found.push(`<${m[3]}>`);
|
|
215
218
|
else if (m[4]) found.push(`$t(${m[4]})`);
|
|
216
219
|
}
|
|
@@ -245,10 +248,46 @@ export function reviewStatus(
|
|
|
245
248
|
return review.verdict === "APPROVED" ? "approved" : "needs_change";
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
/**
|
|
249
|
-
*
|
|
251
|
+
/**
|
|
252
|
+
* Whether `key` lies in `area`: the key IS the area, or sits under it — `legal.terms`
|
|
253
|
+
* and kastlan's `legal:terms` are in `legal`, `legalese.title` is not.
|
|
254
|
+
*
|
|
255
|
+
* 0.28: the kit's own words of that area too — `kit.legal.sections.terms.law.body(…)`
|
|
256
|
+
* is in `legal`. All three apps list the kit's labels under `kit.`
|
|
257
|
+
* (`flattenStrings(kitLabelStrings(labels), "kit.")`), and since 0.28 the kit writes
|
|
258
|
+
* the shared legal sections, so a lawyer with a `["legal"]` grant who was not shown
|
|
259
|
+
* `kit.legal.*` would not see half of the Privacy Policy — and the server would refuse
|
|
260
|
+
* the verdicts (docs/legal-harmonization.md §7.5). server-kit's `in_areas` and the apps'
|
|
261
|
+
* copies of it apply the same rule.
|
|
262
|
+
*/
|
|
250
263
|
export function keyInArea(key: string, area: string): boolean {
|
|
251
|
-
return
|
|
264
|
+
return (
|
|
265
|
+
key === area || key.startsWith(`${area}.`) || key.startsWith(`${area}:`) || key.startsWith(`kit.${area}.`)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The area of `areas` that `key` lies in ({@link keyInArea}), or `undefined` — the
|
|
271
|
+
* translation page's grouping by the same rule the filter and the server use, so
|
|
272
|
+
* `kit.legal.sections.privacy.rights.lead` is grouped under `legal` with the app's own
|
|
273
|
+
* legal texts instead of under `kit.legal`:
|
|
274
|
+
*
|
|
275
|
+
* const namespaceOf = (key: string) => reviewAreaOf(key, ["legal"]) ?? ownNamespaceOf(key);
|
|
276
|
+
*
|
|
277
|
+
* Where two areas both hold the key (`legal` and `legal.privacy`), the longer — the more
|
|
278
|
+
* specific — wins, whatever their order. `inArea` is the app's own rule, as for
|
|
279
|
+
* {@link keyInAreas}.
|
|
280
|
+
*/
|
|
281
|
+
export function reviewAreaOf(
|
|
282
|
+
key: string,
|
|
283
|
+
areas: readonly string[],
|
|
284
|
+
inArea: (key: string, area: string) => boolean = keyInArea,
|
|
285
|
+
): string | undefined {
|
|
286
|
+
let found: string | undefined;
|
|
287
|
+
for (const area of areas) {
|
|
288
|
+
if (inArea(key, area) && (found === undefined || area.length > found.length)) found = area;
|
|
289
|
+
}
|
|
290
|
+
return found;
|
|
252
291
|
}
|
|
253
292
|
|
|
254
293
|
/**
|