@semics-tech/emdash-esign 0.0.0-stage → 1.0.0-rc.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/LICENSE +21 -0
- package/README.md +104 -2
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +343 -0
- package/dist/manifest.json +351 -0
- package/dist/plugin.d.mts +7 -0
- package/dist/plugin.mjs +38 -0
- package/dist/ts/astro/lib/call.d.ts +56 -0
- package/dist/ts/astro/lib/call.js +138 -0
- package/dist/ts/integration/index.d.ts +32 -0
- package/dist/ts/integration/index.js +49 -0
- package/dist/ts/ui/index.d.ts +6 -0
- package/dist/ts/ui/index.js +35 -0
- package/emdash-plugin.jsonc +144 -0
- package/package.json +69 -3
- package/src/astro/SelfServeBlock.astro +84 -0
- package/src/astro/SignPage.astro +53 -0
- package/src/astro/SignedCopyPage.astro +48 -0
- package/src/astro/SigningCeremony.astro +264 -0
- package/src/astro/SnapshotBlocks.astro +35 -0
- package/src/astro/SnapshotInline.astro +28 -0
- package/src/astro/VerifyDocument.astro +70 -0
- package/src/astro/VerifyPage.astro +41 -0
- package/src/astro/index.js +21 -0
- package/src/astro/lib/assets.ts +156 -0
- package/src/astro/lib/call.ts +205 -0
- package/src/astro/lib/csp.ts +57 -0
- package/src/astro/lib/csrf.ts +33 -0
- package/src/astro/lib/markdown.ts +363 -0
- package/src/astro/lib/self-serve.ts +149 -0
- package/src/astro/lib/sign.ts +421 -0
- package/src/astro/self-serve-endpoint.ts +11 -0
- package/src/astro/virtual.d.ts +10 -0
- package/src/client/ceremony.d.ts +31 -0
- package/src/client/ceremony.js +218 -0
- package/src/client/index.ts +5 -0
- package/src/client/pad.d.ts +24 -0
- package/src/client/pad.js +232 -0
- package/src/client/verify.d.ts +3 -0
- package/src/client/verify.js +88 -0
- package/src/i18n/ar.ts +132 -0
- package/src/i18n/en.ts +136 -0
- package/src/i18n/index.ts +121 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translations of the E-sign UI kit (T102, SU-NFR-06). The UI half only: the sandboxed core answers with
|
|
3
|
+
* codes (`SIGNING_TOKEN_EXPIRED`, ...) and never ships a message table (R51, R58). Components take a
|
|
4
|
+
* `locale` (prop, else `Astro.currentLocale`, the page's locale when the site configures Astro i18n, else
|
|
5
|
+
* `en-GB`); its language picks the table here, falling back to English, and its direction sets `dir` on
|
|
6
|
+
* each component. A copy of the Events kit's module (packages never import each other, R35).
|
|
7
|
+
*
|
|
8
|
+
* Client scripts carry no text: the page renders the messages a script needs into a `data-messages`
|
|
9
|
+
* attribute (`clientMessages`). Adding a language: `docs/i18n.md`.
|
|
10
|
+
*/
|
|
11
|
+
import { ar } from "./ar.ts";
|
|
12
|
+
import { en, type MessageKey } from "./en.ts";
|
|
13
|
+
|
|
14
|
+
export type { MessageKey };
|
|
15
|
+
|
|
16
|
+
/** Extra plural forms a language may need beyond English's `one` and `other`. */
|
|
17
|
+
type PluralForm = "zero" | "two" | "few" | "many";
|
|
18
|
+
|
|
19
|
+
/** A language's table: every English key, plus any extra plural forms. */
|
|
20
|
+
export type Messages = { [K in MessageKey]: string } & { [extra: `${string}.${PluralForm}`]: string };
|
|
21
|
+
|
|
22
|
+
/** A key with plural forms, named without its `.one` / `.other` suffix. */
|
|
23
|
+
export type PluralKey = MessageKey extends infer K ? (K extends `${infer Base}.other` ? Base : never) : never;
|
|
24
|
+
|
|
25
|
+
export type Vars = Record<string, string | number>;
|
|
26
|
+
|
|
27
|
+
/** The languages the kit ships, by BCP 47 language subtag. */
|
|
28
|
+
export const LANGUAGES: Record<string, Messages> = { en, ar };
|
|
29
|
+
|
|
30
|
+
/** Languages written right to left (whether or not a table ships for them yet). */
|
|
31
|
+
const RTL = new Set(["ar", "dv", "fa", "he", "ku", "ps", "sd", "ug", "ur", "yi"]);
|
|
32
|
+
|
|
33
|
+
/** The table's language for a locale (`ar-EG` -> `ar`); English when there is no table for it. */
|
|
34
|
+
export function languageOf(locale?: string | null): string {
|
|
35
|
+
const base = (locale ?? "").trim().toLowerCase().split(/[-_]/)[0] ?? "";
|
|
36
|
+
return Object.hasOwn(LANGUAGES, base) ? base : "en";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The writing direction of what a locale's page shows (English text for an unknown language). */
|
|
40
|
+
export function directionOf(locale?: string | null): "ltr" | "rtl" {
|
|
41
|
+
return RTL.has(languageOf(locale)) ? "rtl" : "ltr";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `{name}` placeholders filled from `vars`; unknown ones are left as they are. */
|
|
45
|
+
export function fill(text: string, vars?: Vars): string {
|
|
46
|
+
return vars ? text.replace(/\{(\w+)\}/g, (whole, name: string) => (name in vars ? String(vars[name]) : whole)) : text;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Translator {
|
|
50
|
+
/** The table's language (`en`, `ar`): the value for `lang` attributes. */
|
|
51
|
+
lang: string;
|
|
52
|
+
dir: "ltr" | "rtl";
|
|
53
|
+
t(key: MessageKey, vars?: Vars): string;
|
|
54
|
+
/** The plural form of `key` for `count` by the language's rules (`count` is also a placeholder). */
|
|
55
|
+
plural(key: PluralKey, count: number, vars?: Vars): string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tables = LANGUAGES as Record<string, Record<string, string>>;
|
|
59
|
+
|
|
60
|
+
export function translator(locale?: string | null): Translator {
|
|
61
|
+
const lang = languageOf(locale);
|
|
62
|
+
const table = tables[lang] ?? tables.en;
|
|
63
|
+
const lookup = (key: string) => table?.[key] ?? tables.en?.[key] ?? key;
|
|
64
|
+
const rules = new Intl.PluralRules(lang);
|
|
65
|
+
return {
|
|
66
|
+
lang,
|
|
67
|
+
dir: directionOf(lang),
|
|
68
|
+
t: (key, vars) => fill(lookup(key), vars),
|
|
69
|
+
plural: (key, count, vars) => {
|
|
70
|
+
const form = `${key}.${rules.select(count)}`;
|
|
71
|
+
const text = table && Object.hasOwn(table, form) ? (table[form] as string) : lookup(`${key}.other`);
|
|
72
|
+
return fill(text, { count: new Intl.NumberFormat(lang).format(count), ...vars });
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One message in a locale's language (English fallback). */
|
|
78
|
+
export function t(locale: string | null | undefined, key: MessageKey, vars?: Vars): string {
|
|
79
|
+
return translator(locale).t(key, vars);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The messages under `prefix` (the prefix dropped), plus every key under each of `more` (kept whole), for a
|
|
84
|
+
* client script's `data-messages` attribute.
|
|
85
|
+
*/
|
|
86
|
+
export function clientMessages(
|
|
87
|
+
locale: string | null | undefined,
|
|
88
|
+
prefix: string,
|
|
89
|
+
...more: string[]
|
|
90
|
+
): Record<string, string> {
|
|
91
|
+
const table = tables[languageOf(locale)] ?? {};
|
|
92
|
+
const out: Record<string, string> = {};
|
|
93
|
+
for (const key of Object.keys(en)) {
|
|
94
|
+
const text = table[key] ?? (en as Record<string, string>)[key] ?? "";
|
|
95
|
+
if (key.startsWith(prefix)) out[key.slice(prefix.length)] = text;
|
|
96
|
+
else if (more.some((p) => key.startsWith(p))) out[key] = text;
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The first locale given (a prop, `Astro.currentLocale`, the integration's option), else `en-GB`. */
|
|
102
|
+
export function pickLocale(...candidates: Array<string | null | undefined>): string {
|
|
103
|
+
return candidates.find((c): c is string => typeof c === "string" && c.trim() !== "") ?? "en-GB";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A message as text runs and the `{placeholders}` left in it, so a component can put an element (a link,
|
|
108
|
+
* `<strong>`) where the language puts the placeholder: `{segments(t(key)).map((s) => s.slot === "name" ? <b/> : s.text)}`.
|
|
109
|
+
*/
|
|
110
|
+
export function segments(text: string): Array<{ text: string; slot?: string }> {
|
|
111
|
+
return text
|
|
112
|
+
.split(/(\{\w+\})/)
|
|
113
|
+
.filter((part) => part !== "")
|
|
114
|
+
.map((part) => (/^\{\w+\}$/.test(part) ? { text: "", slot: part.slice(1, -1) } : { text: part }));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A message split around one `{placeholder}`, so a component can put an element (a `<time>`) there. */
|
|
118
|
+
export function around(text: string, name: string): [string, string] {
|
|
119
|
+
const at = text.indexOf(`{${name}}`);
|
|
120
|
+
return at === -1 ? [text, ""] : [text.slice(0, at), text.slice(at + name.length + 2)];
|
|
121
|
+
}
|