@eifi1/ui-kit 0.28.0 → 0.29.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.
Files changed (166) hide show
  1. package/README.md +22 -4
  2. package/dist/auth/accept-invitation.d.ts +90 -0
  3. package/dist/auth/accept-invitation.js +117 -0
  4. package/dist/auth/accept-invitation.js.map +1 -0
  5. package/dist/auth/auth-errors.d.ts +60 -0
  6. package/dist/auth/auth-errors.js +32 -0
  7. package/dist/auth/auth-errors.js.map +1 -0
  8. package/dist/auth/complete-name-dialog.d.ts +71 -0
  9. package/dist/auth/complete-name-dialog.js +168 -0
  10. package/dist/auth/complete-name-dialog.js.map +1 -0
  11. package/dist/auth/email-tag.d.ts +60 -0
  12. package/dist/auth/email-tag.js +23 -0
  13. package/dist/auth/email-tag.js.map +1 -0
  14. package/dist/auth/forgot-password-form.d.ts +79 -0
  15. package/dist/auth/forgot-password-form.js +120 -0
  16. package/dist/auth/forgot-password-form.js.map +1 -0
  17. package/dist/auth/form-rules.d.ts +27 -0
  18. package/dist/auth/form-rules.js +23 -0
  19. package/dist/auth/form-rules.js.map +1 -0
  20. package/dist/auth/new-password-fields.d.ts +38 -0
  21. package/dist/auth/new-password-fields.js +54 -0
  22. package/dist/auth/new-password-fields.js.map +1 -0
  23. package/dist/auth/not-found-page.d.ts +57 -0
  24. package/dist/auth/not-found-page.js +40 -0
  25. package/dist/auth/not-found-page.js.map +1 -0
  26. package/dist/auth/register-form.d.ts +86 -0
  27. package/dist/auth/register-form.js +268 -0
  28. package/dist/auth/register-form.js.map +1 -0
  29. package/dist/auth/reset-password-form.d.ts +124 -0
  30. package/dist/auth/reset-password-form.js +164 -0
  31. package/dist/auth/reset-password-form.js.map +1 -0
  32. package/dist/auth/sign-in-form.d.ts +243 -0
  33. package/dist/auth/sign-in-form.js +345 -0
  34. package/dist/auth/sign-in-form.js.map +1 -0
  35. package/dist/auth/status-parts.d.ts +78 -0
  36. package/dist/auth/status-parts.js +83 -0
  37. package/dist/auth/status-parts.js.map +1 -0
  38. package/dist/auth/verify-email.d.ts +121 -0
  39. package/dist/auth/verify-email.js +207 -0
  40. package/dist/auth/verify-email.js.map +1 -0
  41. package/dist/chart.d.ts +17 -2
  42. package/dist/components/amount-input.d.ts +18 -2
  43. package/dist/components/button-group.d.ts +18 -2
  44. package/dist/components/calculator.d.ts +18 -2
  45. package/dist/components/confirm-dialog.d.ts +18 -2
  46. package/dist/components/copy-button.d.ts +18 -2
  47. package/dist/components/danger-confirm.d.ts +18 -2
  48. package/dist/components/facing-pair.d.ts +18 -2
  49. package/dist/components/file-button.d.ts +18 -2
  50. package/dist/components/file-dropzone.d.ts +18 -2
  51. package/dist/components/form-actions.d.ts +18 -2
  52. package/dist/components/iban-input.d.ts +18 -2
  53. package/dist/components/language-select.d.ts +18 -9
  54. package/dist/components/legal-page.d.ts +26 -4
  55. package/dist/components/legal-page.js +24 -5
  56. package/dist/components/legal-page.js.map +1 -1
  57. package/dist/components/legal.d.ts +18 -2
  58. package/dist/components/legal.js +49 -5
  59. package/dist/components/legal.js.map +1 -1
  60. package/dist/components/money-field.d.ts +18 -2
  61. package/dist/components/number-field.d.ts +18 -2
  62. package/dist/components/number-input.d.ts +18 -2
  63. package/dist/components/numpad-sheet.d.ts +18 -2
  64. package/dist/components/phone-input.d.ts +18 -2
  65. package/dist/components/series-chart.d.ts +18 -2
  66. package/dist/components/settings-fields.d.ts +15 -2
  67. package/dist/components/share-card.d.ts +18 -2
  68. package/dist/components/text-link.d.ts +18 -2
  69. package/dist/components/time-input.d.ts +18 -2
  70. package/dist/components/ui.d.ts +17 -1
  71. package/dist/components/user-avatar.d.ts +16 -2
  72. package/dist/components/user-avatar.js +7 -3
  73. package/dist/components/user-avatar.js.map +1 -1
  74. package/dist/feedback/feedback-attachment.d.ts +18 -2
  75. package/dist/feedback/feedback-context.d.ts +18 -2
  76. package/dist/feedback/feedback-crash.d.ts +18 -2
  77. package/dist/feedback/feedback-dialog.d.ts +18 -2
  78. package/dist/feedback/feedback-inbox.d.ts +18 -2
  79. package/dist/feedback/feedback-labels.d.ts +18 -2
  80. package/dist/feedback/feedback-menu.d.ts +18 -2
  81. package/dist/feedback/feedback-record.d.ts +18 -2
  82. package/dist/feedback/feedback-row-detail.d.ts +18 -2
  83. package/dist/feedback/feedback-status-undo.d.ts +18 -2
  84. package/dist/feedback/feedback-submit.d.ts +18 -2
  85. package/dist/feedback/feedback-swipe.d.ts +18 -2
  86. package/dist/feedback/feedback-table.d.ts +18 -2
  87. package/dist/feedback/feedback-thread.d.ts +18 -2
  88. package/dist/feedback.d.ts +18 -2
  89. package/dist/hooks/use-file-drop.d.ts +18 -2
  90. package/dist/i18n/defaults.d.ts +18 -2
  91. package/dist/i18n/defaults.js +18 -0
  92. package/dist/i18n/defaults.js.map +1 -1
  93. package/dist/i18n/german.d.ts +18 -2
  94. package/dist/i18n/german.js +145 -0
  95. package/dist/i18n/german.js.map +1 -1
  96. package/dist/i18n/kit-labels.d.ts +18 -2
  97. package/dist/i18n/kit-labels.js.map +1 -1
  98. package/dist/i18n/languages.d.ts +13 -201
  99. package/dist/i18n/locales/de-CH.d.ts +18 -2
  100. package/dist/i18n/locales/en.d.ts +18 -2
  101. package/dist/i18n/locales/es.d.ts +18 -2
  102. package/dist/i18n/locales/es.js +147 -3
  103. package/dist/i18n/locales/es.js.map +1 -1
  104. package/dist/i18n/locales/fr.d.ts +18 -2
  105. package/dist/i18n/locales/fr.js +147 -0
  106. package/dist/i18n/locales/fr.js.map +1 -1
  107. package/dist/i18n/locales/hu.d.ts +18 -2
  108. package/dist/i18n/locales/hu.js +149 -3
  109. package/dist/i18n/locales/hu.js.map +1 -1
  110. package/dist/i18n/locales/it.d.ts +18 -2
  111. package/dist/i18n/locales/it.js +146 -1
  112. package/dist/i18n/locales/it.js.map +1 -1
  113. package/dist/i18n/locales/zh.d.ts +18 -2
  114. package/dist/i18n/locales/zh.js +146 -1
  115. package/dist/i18n/locales/zh.js.map +1 -1
  116. package/dist/i18n/review.d.ts +18 -2
  117. package/dist/i18n/review.js +17 -7
  118. package/dist/i18n/review.js.map +1 -1
  119. package/dist/index.d.ts +13 -2
  120. package/dist/index.js +49 -1
  121. package/dist/index.js.map +1 -1
  122. package/dist/lib/person-name.d.ts +69 -0
  123. package/dist/lib/person-name.js +65 -0
  124. package/dist/lib/person-name.js.map +1 -0
  125. package/dist/{kit-labels-SBN0biPl.d.ts → register-form-V1OeNlMr.d.ts} +423 -6
  126. package/dist/rhf/fields.d.ts +18 -2
  127. package/dist/rhf/form.d.ts +18 -2
  128. package/dist/rhf.d.ts +18 -2
  129. package/dist/shell/app-shell.d.ts +18 -2
  130. package/dist/shell/company-switcher.d.ts +59 -0
  131. package/dist/shell/company-switcher.js +85 -0
  132. package/dist/shell/company-switcher.js.map +1 -0
  133. package/dist/shell/top-bar-brand.d.ts +18 -2
  134. package/dist/shell/topbar-action-menu.d.ts +1 -0
  135. package/dist/shell.d.ts +12 -2
  136. package/dist/wizard/stepper-nav.d.ts +18 -2
  137. package/dist/wizard.d.ts +18 -2
  138. package/package.json +1 -1
  139. package/src/auth/accept-invitation.tsx +227 -0
  140. package/src/auth/auth-errors.ts +105 -0
  141. package/src/auth/complete-name-dialog.tsx +253 -0
  142. package/src/auth/email-tag.ts +75 -0
  143. package/src/auth/forgot-password-form.tsx +213 -0
  144. package/src/auth/form-rules.ts +40 -0
  145. package/src/auth/new-password-fields.tsx +85 -0
  146. package/src/auth/not-found-page.tsx +98 -0
  147. package/src/auth/register-form.tsx +459 -0
  148. package/src/auth/reset-password-form.tsx +337 -0
  149. package/src/auth/sign-in-form.tsx +687 -0
  150. package/src/auth/status-parts.tsx +138 -0
  151. package/src/auth/verify-email.tsx +358 -0
  152. package/src/components/legal-page.tsx +35 -0
  153. package/src/components/legal.tsx +93 -7
  154. package/src/components/user-avatar.tsx +22 -4
  155. package/src/i18n/defaults.ts +18 -0
  156. package/src/i18n/german.ts +153 -0
  157. package/src/i18n/kit-labels.tsx +27 -0
  158. package/src/i18n/locales/es.ts +156 -3
  159. package/src/i18n/locales/fr.ts +157 -0
  160. package/src/i18n/locales/hu.ts +159 -3
  161. package/src/i18n/locales/it.ts +154 -1
  162. package/src/i18n/locales/zh.ts +149 -1
  163. package/src/i18n/review.ts +15 -6
  164. package/src/index.ts +63 -1
  165. package/src/lib/person-name.ts +157 -0
  166. package/src/shell/company-switcher.tsx +140 -0
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext } from "react";
2
- import type { ReactNode } from "react";
2
+ import type { MouseEvent, ReactNode } from "react";
3
3
 
4
4
  import { useKitLabels } from "../i18n/kit-labels";
5
5
  import type { LabelOverride } from "../i18n/kit-labels";
@@ -66,6 +66,14 @@ export interface LegalOperator {
66
66
  country: string;
67
67
  /** The contact address the imprint and the privacy policy print. */
68
68
  email: string;
69
+ /**
70
+ * The language whose version of the legal texts is BINDING ("de-CH"), a BCP 47 code
71
+ * (0.28.1, Marcel 2026-10-05). The lawyer reviews that one; the others are translations
72
+ * for convenience. With it, {@link LegalPage} shows a note on every page read in
73
+ * another language, and the terms' `language` section says which version prevails.
74
+ * Only the language subtag counts: "de-CH" and "de" are the same language here.
75
+ */
76
+ bindingLanguage?: string;
69
77
  }
70
78
 
71
79
  /**
@@ -78,10 +86,42 @@ export interface LegalOperator {
78
86
  */
79
87
  export type LegalOperatorText = Readonly<Record<keyof LegalOperator, string>>;
80
88
 
89
+ /** The language subtag of a BCP 47 code, lower-cased: "de-CH" → "de". */
90
+ export function baseLanguage(code: string): string {
91
+ return code.split(/[-_]/)[0]!.toLowerCase();
92
+ }
93
+
94
+ const languageNamers = new Map<string, Intl.DisplayNames | null>();
95
+
96
+ /** The name of a language in `locale` — "Deutsch", "German", "allemand", "德语" — from its
97
+ * language subtag, so "de-CH" is plain "German", not "Swiss High German". The code itself
98
+ * when the runtime cannot name it. */
99
+ function languageName(code: string, locale: string): string {
100
+ let namer = languageNamers.get(locale);
101
+ if (namer === undefined) {
102
+ try {
103
+ namer = new Intl.DisplayNames([locale], { type: "language" });
104
+ } catch {
105
+ namer = null;
106
+ }
107
+ languageNamers.set(locale, namer);
108
+ }
109
+ const base = baseLanguage(code);
110
+ try {
111
+ return namer?.of(base) ?? base;
112
+ } catch {
113
+ return base;
114
+ }
115
+ }
116
+
81
117
  /** @internal The operator as the texts read it: its country named in `locale` (English
82
118
  * without one — `countryName`'s rule, so an app that set no locale reads English). */
83
119
  export function legalOperatorText(operator: LegalOperator, locale: string = "en"): LegalOperatorText {
84
- return { ...operator, country: countryName(operator.country, locale) };
120
+ return {
121
+ ...operator,
122
+ country: countryName(operator.country, locale),
123
+ bindingLanguage: operator.bindingLanguage ? languageName(operator.bindingLanguage, locale) : "",
124
+ };
85
125
  }
86
126
 
87
127
  // ── Labels ────────────────────────────────────────────────────────────────────
@@ -142,6 +182,16 @@ export interface LegalLabels {
142
182
  beta: string;
143
183
  privacy: string;
144
184
  };
185
+ /**
186
+ * 0.28.1: on a page read in another language than the operator's `bindingLanguage` —
187
+ * `note` under the title, `show` on the button that switches to the binding version
188
+ * (when the app passes `onShowBindingLanguage`). `o.bindingLanguage` is the binding
189
+ * language's NAME in the reader's language ("German", "allemand").
190
+ */
191
+ translation: {
192
+ note: (o: LegalOperatorText) => string;
193
+ show: (o: LegalOperatorText) => string;
194
+ };
145
195
  /** The kit's sections (§4.3), by page and section key. */
146
196
  sections: {
147
197
  impressum: {
@@ -164,6 +214,8 @@ export interface LegalLabels {
164
214
  warranty: LegalTextSectionLabels;
165
215
  liability: LegalTextSectionLabels;
166
216
  changes: LegalTextSectionLabels;
217
+ /** 0.28.1: which language's version prevails — `o.bindingLanguage`, named. */
218
+ language: LegalOperatorSectionLabels;
167
219
  /** The place of jurisdiction is the operator's `{city} ({region}), {country}`. */
168
220
  law: LegalOperatorSectionLabels;
169
221
  };
@@ -189,6 +241,11 @@ export const DEFAULT_LEGAL_LABELS: LegalLabels = {
189
241
  privacy:
190
242
  "Closed beta. The legal wording below has not been reviewed by a lawyer yet and will be before a public launch. The technical descriptions — what is stored, where, and who can read it — describe what the software actually does today, and are meant to be checked against it.",
191
243
  },
244
+ translation: {
245
+ note: (o) =>
246
+ `This is a translation for your convenience. The ${o.bindingLanguage} version is binding.`,
247
+ show: (o) => `Show the ${o.bindingLanguage} version`,
248
+ },
192
249
  sections: {
193
250
  impressum: {
194
251
  operator: {
@@ -243,6 +300,11 @@ export const DEFAULT_LEGAL_LABELS: LegalLabels = {
243
300
  title: "Changes to these terms",
244
301
  body: "These terms may be updated as the service evolves. Material changes will be announced by email or in the app; continued use after a change means you accept it.",
245
302
  },
303
+ language: {
304
+ title: "Language",
305
+ body: (o) =>
306
+ `These terms are written in ${o.bindingLanguage}. Translations into other languages are provided for convenience only; where a translation differs, the ${o.bindingLanguage} version prevails.`,
307
+ },
246
308
  law: {
247
309
  title: "Governing law",
248
310
  body: (o) =>
@@ -375,6 +437,9 @@ export interface LegalLinksProps {
375
437
  * links, so in a plain container — Kurvenschmiede's account-menu `<li>` — they ran
376
438
  * together as "ImpressumDatenschutzerklärungNutz…" and overflowed). Default true. */
377
439
  nav?: boolean;
440
+ /** Called on a click on any of the links, before it navigates — an account menu closes
441
+ * itself here (0.28.1, keksdose: it had to catch the click on a wrapping `<div>`). */
442
+ onLinkClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
378
443
  labels?: LabelOverride<LegalLabels>;
379
444
  className?: string;
380
445
  }
@@ -396,12 +461,19 @@ function defaultLinks(labels: LegalLabels): LegalLink[] {
396
461
  * already hears three links, and "middle dot" between them is noise. They are flex items
397
462
  * of the row like the links, so the row's gap spaces them evenly.
398
463
  */
399
- export function LegalLinks({ links, currentHref, nav = true, labels: labelsProp, className }: LegalLinksProps) {
464
+ export function LegalLinks({
465
+ links,
466
+ currentHref,
467
+ nav = true,
468
+ onLinkClick,
469
+ labels: labelsProp,
470
+ className,
471
+ }: LegalLinksProps) {
400
472
  const labels = useLegalLabels(labelsProp);
401
473
  const shown = links ?? defaultLinks(labels);
402
474
  const items = shown.flatMap((link, i) => [
403
475
  ...(i > 0 ? [<span key={`sep-${link.href}`} aria-hidden="true">·</span>] : []),
404
- <TextLink key={link.href} href={link.href} tone="muted" current={link.href === currentHref}>
476
+ <TextLink key={link.href} href={link.href} tone="muted" current={link.href === currentHref} onClick={onLinkClick}>
405
477
  {link.label}
406
478
  </TextLink>,
407
479
  ]);
@@ -425,6 +497,14 @@ export interface LegalFooterProps {
425
497
  /** Marks that link `aria-current="page"`. {@link LegalPage}'s default footer passes
426
498
  * its own page. */
427
499
  currentHref?: string;
500
+ /**
501
+ * Default true: the footer is a `<footer>`. `false` draws a `<div>` — for a host that is
502
+ * a footer already: `AuthLayout`'s `footer` slot is one, and HTML forbids a footer inside
503
+ * a footer (0.28.1, kastlan). On an `AuthLayout` page the usual choice is simpler still:
504
+ * `footer={<LegalLinks nav={false} />}` with `footerLabel` set to the kit's `navLabel`
505
+ * ({@link useLegalLabels}).
506
+ */
507
+ landmark?: boolean;
428
508
  className?: string;
429
509
  labels?: LabelOverride<LegalLabels>;
430
510
  }
@@ -440,9 +520,10 @@ export interface LegalFooterProps {
440
520
  * only. Its palette classes (`border-slate-200 bg-white dark:…`) became the surface and
441
521
  * border tokens, so it follows the theme like the rest of the kit.
442
522
  */
443
- export function LegalFooter({ children, links, currentHref, className, labels }: LegalFooterProps) {
523
+ export function LegalFooter({ children, links, currentHref, landmark = true, className, labels }: LegalFooterProps) {
524
+ const Root = landmark ? "footer" : "div";
444
525
  return (
445
- <footer className={cn("border-t border-[var(--border)] bg-[var(--bg-surface)]", className)}>
526
+ <Root className={cn("border-t border-[var(--border)] bg-[var(--bg-surface)]", className)}>
446
527
  <div className="mx-auto max-w-5xl px-4 py-8 text-sm text-[var(--text-muted)]">
447
528
  <LegalLinks
448
529
  links={links}
@@ -452,7 +533,7 @@ export function LegalFooter({ children, links, currentHref, className, labels }:
452
533
  />
453
534
  {children !== undefined && children !== null && <p className="mt-4 text-center">{children}</p>}
454
535
  </div>
455
- </footer>
536
+ </Root>
456
537
  );
457
538
  }
458
539
 
@@ -526,6 +607,9 @@ export interface LegalLayoutProps {
526
607
  * than the others (its technical sections describe running code).
527
608
  */
528
609
  notice?: ReactNode;
610
+ /** 0.28.1: under the title, before the notice — {@link LegalPage}'s note that this
611
+ * page is a translation and which language is binding. */
612
+ translationNote?: ReactNode;
529
613
  /** The sections — {@link LegalSection}s. */
530
614
  children: ReactNode;
531
615
  /** The three pages, linked at the end of the column with the current one marked.
@@ -543,6 +627,7 @@ export function LegalLayout({
543
627
  title,
544
628
  back,
545
629
  notice,
630
+ translationNote,
546
631
  children,
547
632
  links,
548
633
  currentHref,
@@ -554,6 +639,7 @@ export function LegalLayout({
554
639
  <article className={cn("mx-auto w-full max-w-3xl px-4 py-12", className)}>
555
640
  {back !== undefined && <div className="mb-4 text-sm">{back}</div>}
556
641
  <Heading className="text-3xl font-bold tracking-tight break-words text-[var(--text-primary)]">{title}</Heading>
642
+ {translationNote !== undefined && translationNote !== null && <div className="mt-3">{translationNote}</div>}
557
643
  {notice !== undefined && notice !== null && (
558
644
  <AlertBanner tone="warning" role="note" className="mt-4">
559
645
  {notice}
@@ -1,5 +1,8 @@
1
1
  import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  import { cn } from "../lib/cn";
3
+ import { personInitials } from "../lib/person-name";
4
+ import type { PersonName } from "../lib/person-name";
5
+ import { useKitLocale } from "../i18n/kit-labels";
3
6
  import { StatusDot } from "./status-dot";
4
7
  import type { StatusDotSize, StatusDotTone } from "./status-dot";
5
8
 
@@ -43,10 +46,23 @@ export interface UserAvatarBadge {
43
46
  }
44
47
 
45
48
  /** A `<span>`'s props, minus `children`: the content is the initials this computes
46
- * from `name`/`email`, so there is nothing for a caller to put inside. */
49
+ * from `person`/`name`/`email`, so there is nothing for a caller to put inside. */
47
50
  export interface UserAvatarProps extends Omit<ComponentPropsWithoutRef<"span">, "children"> {
48
51
  name?: string | null;
49
52
  email?: string | null;
53
+ /**
54
+ * The person's first and last name (0.29.0, docs/auth-harmonization.md §3.2) —
55
+ * preferred over `name` whenever either part is filled in. The initials follow the
56
+ * READER's language, the provider's `locale`: "AE" for Ada Example, "EA" in Hungarian
57
+ * (family name first), the family name's first character alone in Chinese. See
58
+ * {@link personInitials}.
59
+ *
60
+ * `name` stays the fallback for an API that still sends only `display_name`, and
61
+ * `email` after it: a person whose two parts are both blank falls through to them.
62
+ * A migrated user (the whole old display name in `first`, no `last`, §3.3) keeps the
63
+ * initials they had.
64
+ */
65
+ person?: PersonName | null;
50
66
  size?: keyof typeof AVATAR_SIZES;
51
67
  /**
52
68
  * A status dot in the top-end corner (the top-right in LTR, top-left in RTL), ringed
@@ -64,7 +80,9 @@ export interface UserAvatarProps extends Omit<ComponentPropsWithoutRef<"span">,
64
80
  * A round initials avatar — the common user chip shared across apps (feedback
65
81
  * #333). Purely presentational; wrap it in a button for the account menu trigger.
66
82
  */
67
- export function UserAvatar({ name, email, size = "md", badge, className, ...rest }: UserAvatarProps) {
83
+ export function UserAvatar({ name, email, person, size = "md", badge, className, ...rest }: UserAvatarProps) {
84
+ const locale = useKitLocale();
85
+ const initials = (person ? personInitials(person, locale) : "") || avatarInitials(name, email);
68
86
  if (badge) {
69
87
  // A wrapper, because the chip itself is `aria-hidden` and the badge label must not
70
88
  // be. `className` stays on the chip (a size override still sizes the circle); the
@@ -80,7 +98,7 @@ export function UserAvatar({ name, email, size = "md", badge, className, ...rest
80
98
  className,
81
99
  )}
82
100
  >
83
- {avatarInitials(name, email)}
101
+ {initials}
84
102
  </span>
85
103
  <StatusDot
86
104
  ring
@@ -109,7 +127,7 @@ export function UserAvatar({ name, email, size = "md", badge, className, ...rest
109
127
  className,
110
128
  )}
111
129
  >
112
- {avatarInitials(name, email)}
130
+ {initials}
113
131
  </span>
114
132
  );
115
133
  }
@@ -61,6 +61,15 @@ import { DEFAULT_IMAGE_GRID_LABELS } from "../components/image-grid";
61
61
  import { DEFAULT_LIGHTBOX_LABELS } from "../components/lightbox";
62
62
  import { DEFAULT_WRITE_LOCK_LABELS } from "../components/write-lock";
63
63
  import { DEFAULT_LEGAL_LABELS } from "../components/legal";
64
+ import { DEFAULT_SIGN_IN_LABELS } from "../auth/sign-in-form";
65
+ import { DEFAULT_REGISTER_LABELS } from "../auth/register-form";
66
+ import { DEFAULT_COMPLETE_NAME_LABELS } from "../auth/complete-name-dialog";
67
+ import { DEFAULT_FORGOT_PASSWORD_LABELS } from "../auth/forgot-password-form";
68
+ import { DEFAULT_RESET_PASSWORD_LABELS } from "../auth/reset-password-form";
69
+ import { DEFAULT_VERIFY_EMAIL_LABELS } from "../auth/verify-email";
70
+ import { DEFAULT_NOT_FOUND_LABELS } from "../auth/not-found-page";
71
+ import { DEFAULT_ACCEPT_INVITATION_LABELS } from "../auth/accept-invitation";
72
+ import { DEFAULT_COMPANY_SWITCHER_LABELS } from "../shell/company-switcher";
64
73
  import { DEFAULT_ACCOUNT_STATE_LABELS } from "../components/account-chips";
65
74
  import { DEFAULT_SHARE_CARD_LABELS } from "../components/share-card";
66
75
  import { DEFAULT_REAUTH_DIALOG_LABELS } from "../components/reauth-dialog";
@@ -168,6 +177,15 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
168
177
  serverWake: DEFAULT_SERVER_WAKE_LABELS,
169
178
  translationReview: DEFAULT_TRANSLATION_REVIEW_LABELS,
170
179
  legal: DEFAULT_LEGAL_LABELS,
180
+ signIn: DEFAULT_SIGN_IN_LABELS,
181
+ register: DEFAULT_REGISTER_LABELS,
182
+ completeName: DEFAULT_COMPLETE_NAME_LABELS,
183
+ forgotPassword: DEFAULT_FORGOT_PASSWORD_LABELS,
184
+ resetPassword: DEFAULT_RESET_PASSWORD_LABELS,
185
+ verifyEmail: DEFAULT_VERIFY_EMAIL_LABELS,
186
+ notFound: DEFAULT_NOT_FOUND_LABELS,
187
+ acceptInvitation: DEFAULT_ACCEPT_INVITATION_LABELS,
188
+ companySwitcher: DEFAULT_COMPANY_SWITCHER_LABELS,
171
189
  characterCount: DEFAULT_CHARACTER_COUNT_LABELS,
172
190
  countrySelect: DEFAULT_COUNTRY_SELECT_LABELS,
173
191
  inlineEdit: DEFAULT_INLINE_EDIT_LABELS,
@@ -779,6 +779,11 @@ export function germanLabels(numberLocale: string): UiKitLabels {
779
779
  privacy:
780
780
  "Geschlossene Beta. Die rechtlichen Formulierungen unten sind noch nicht anwaltlich geprüft; das geschieht vor einem öffentlichen Start. Die technischen Angaben — was gespeichert wird, wo, und wer es lesen kann — beschreiben, was die Software heute tatsächlich tut, und sind ausdrücklich dazu da, daran überprüft zu werden.",
781
781
  },
782
+ translation: {
783
+ note: (o) =>
784
+ `Dies ist eine Übersetzung zu Ihrer Information. Massgebend ist die Fassung auf ${o.bindingLanguage}.`,
785
+ show: (o) => `Fassung auf ${o.bindingLanguage} anzeigen`,
786
+ },
782
787
  sections: {
783
788
  impressum: {
784
789
  operator: {
@@ -833,6 +838,11 @@ export function germanLabels(numberLocale: string): UiKitLabels {
833
838
  title: "Änderungen dieser Bedingungen",
834
839
  body: "Diese Bedingungen können im Zuge der Weiterentwicklung des Dienstes angepasst werden. Wesentliche Änderungen werden per E-Mail oder in der App angekündigt; die weitere Nutzung nach einer Änderung gilt als Zustimmung.",
835
840
  },
841
+ language: {
842
+ title: "Sprache",
843
+ body: (o) =>
844
+ `Diese Bedingungen sind auf ${o.bindingLanguage} verfasst. Übersetzungen in andere Sprachen dienen nur der Information; weicht eine Übersetzung ab, ist die Fassung auf ${o.bindingLanguage} massgebend.`,
845
+ },
836
846
  law: {
837
847
  title: "Anwendbares Recht",
838
848
  body: (o) =>
@@ -841,6 +851,149 @@ export function germanLabels(numberLocale: string): UiKitLabels {
841
851
  },
842
852
  },
843
853
  },
854
+ // 0.29.0 — docs/auth-harmonization.md: keksdose's reviewed de-CH wording (auth.*,
855
+ // verify_email, error.not_found_*, budget_share.join_*), kastlan's for the names and
856
+ // the 2FA prompt; written fresh where neither app had the sentence. Formal "Sie".
857
+ signIn: {
858
+ email: "E-Mail",
859
+ password: "Passwort",
860
+ submit: "Anmelden",
861
+ passkey: "Mit Passkey anmelden",
862
+ forgotPassword: "Passwort vergessen?",
863
+ noAccount: "Noch kein Konto?",
864
+ register: "Registrieren",
865
+ invalidCredentials: "Die E-Mail-Adresse oder das Passwort ist falsch.",
866
+ failed: "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.",
867
+ passkeyFailed: "Passkey-Anmeldung fehlgeschlagen.",
868
+ deactivatedHint: "Konto deaktiviert? Schreiben Sie an {contact}.",
869
+ tagHint: (taggedAddress) =>
870
+ `Mit ${taggedAddress} registriert? Verwenden Sie diese Adresse.`,
871
+ twoFactorTitle: "Zwei-Faktor-Authentifizierung",
872
+ twoFactorIntro: "Geben Sie den Code aus Ihrer Authenticator-App ein.",
873
+ code: "2FA-Code",
874
+ verify: "Bestätigen",
875
+ codeInvalid: "Ungültiger 2FA-Code.",
876
+ setPasswordTitle: "Neues Passwort vergeben",
877
+ setPasswordIntro:
878
+ "Für dieses Konto wurde ein neues Passwort angefordert. Vergeben Sie eines, dann geht es direkt weiter.",
879
+ newPassword: "Neues Passwort",
880
+ confirmPassword: "Neues Passwort wiederholen",
881
+ passwordMismatch: "Die Passwörter stimmen nicht überein.",
882
+ setPasswordSubmit: "Passwort vergeben und anmelden",
883
+ setPasswordFailed: "Das Passwort konnte nicht vergeben werden.",
884
+ expired: "Diese Anmeldung ist abgelaufen. Bitte melden Sie sich erneut an.",
885
+ backToSignIn: "Zurück zur Anmeldung",
886
+ },
887
+ register: {
888
+ firstName: "Vorname",
889
+ lastName: "Nachname",
890
+ email: "E-Mail",
891
+ emailTagUse: (address) => `${address} verwenden`,
892
+ emailTagHint:
893
+ "Viele Anbieter stellen name+kennung@… an dasselbe Postfach zu, damit Post von dieser App filter- und zuordenbar wird. Prüfen Sie vorher, ob Ihrer das tut: Angemeldet wird dann mit der markierten Adresse.",
894
+ invitedEmailHint: "Die Adresse, an die Ihre Einladung gesendet wurde.",
895
+ invitedTagNote: "Die markierte Adresse erhält eine eigene Bestätigungs-E-Mail.",
896
+ password: "Passwort",
897
+ confirmPassword: "Passwort wiederholen",
898
+ passwordMismatch: "Die Passwörter stimmen nicht überein.",
899
+ language: "Sprache",
900
+ submit: "Registrieren",
901
+ haveAccount: "Bereits registriert?",
902
+ signIn: "Anmelden",
903
+ emailTaken: "Ein Konto mit dieser E-Mail-Adresse existiert bereits.",
904
+ registrationClosed:
905
+ "Neue Konten gibt es nur auf Einladung. Bitten Sie den Betreiber, Ihre E-Mail-Adresse einzuladen.",
906
+ invitationInvalid: "Dieser Einladungslink ist ungültig.",
907
+ invitationExpired: "Diese Einladung ist abgelaufen. Bitten Sie um eine neue.",
908
+ failed: "Registrierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
909
+ },
910
+ completeName: {
911
+ title: "Namen vervollständigen",
912
+ description:
913
+ "Vor- und Nachname werden jetzt getrennt erfasst. Prüfen Sie, was eingetragen ist, und ergänzen Sie, was fehlt.",
914
+ firstName: "Vorname",
915
+ lastName: "Nachname",
916
+ save: "Speichern",
917
+ later: "Später",
918
+ failed: "Ihr Name konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.",
919
+ },
920
+ forgotPassword: {
921
+ title: "Passwort vergessen",
922
+ intro:
923
+ "Geben Sie die E-Mail-Adresse Ihres Kontos ein. Wir schicken Ihnen einen Link, mit dem Sie ein neues Passwort vergeben können.",
924
+ email: "E-Mail",
925
+ submit: "Link anfordern",
926
+ sent: (email) => `Falls es ein Konto zu ${email} gibt, ist der Link unterwegs.`,
927
+ sentHint: "Der Link gilt eine Stunde und funktioniert genau einmal. Sehen Sie auch im Spam-Ordner nach.",
928
+ backToSignIn: "Zurück zur Anmeldung",
929
+ error: "Anfrage fehlgeschlagen. Bitte versuchen Sie es später noch einmal.",
930
+ },
931
+ resetPassword: {
932
+ title: "Neues Passwort vergeben",
933
+ checking: "Link wird geprüft…",
934
+ intro: (email) => `Sie vergeben ein neues Passwort für ${email}.`,
935
+ newPassword: "Neues Passwort",
936
+ confirmPassword: "Neues Passwort wiederholen",
937
+ mismatch: "Die Passwörter stimmen nicht überein.",
938
+ submit: "Passwort speichern",
939
+ failed: "Das Passwort konnte nicht geändert werden.",
940
+ noToken: "Dieser Link ist unvollständig.",
941
+ invalid: "Dieser Link ist ungültig oder abgelaufen.",
942
+ invalidHint: "Links gelten eine Stunde und funktionieren nur einmal. Fordern Sie einfach einen neuen an.",
943
+ requestNew: "Neuen Link anfordern",
944
+ success: "Ihr Passwort ist geändert. Sie können sich jetzt damit anmelden.",
945
+ sessionsEnded: "Angemeldete Geräte wurden abgemeldet – dort müssen Sie sich neu anmelden.",
946
+ signIn: "Zur Anmeldung",
947
+ },
948
+ verifyEmail: {
949
+ title: "E-Mail-Adresse bestätigen",
950
+ verifying: "Wird bestätigt…",
951
+ verified: "Ihre E-Mail-Adresse ist bestätigt.",
952
+ continue: "Weiter",
953
+ invalid: "Dieser Bestätigungslink ist ungültig",
954
+ expired: "Dieser Bestätigungslink ist abgelaufen",
955
+ noToken: "Diesem Link fehlt das Bestätigungs-Token.",
956
+ invalidHint: "Der Link ist vermutlich abgelaufen oder wurde schon benutzt.",
957
+ requestInApp: "Sie können in der App einen neuen anfordern.",
958
+ requestHere: "Sie können hier einen neuen anfordern.",
959
+ resend: "Erneut senden",
960
+ resendIn: (seconds) => `In ${n(seconds)} s erneut senden`,
961
+ resent: "Bestätigungs-E-Mail gesendet",
962
+ resendError: "Bestätigungs-E-Mail konnte nicht gesendet werden",
963
+ banner: "Bitte bestätigen Sie Ihre E-Mail-Adresse.",
964
+ dismiss: "Später",
965
+ },
966
+ notFound: {
967
+ title: "Seite nicht gefunden",
968
+ body: "Diese Adresse gibt es (nicht mehr). Vielleicht ein Tippfehler – oder die Seite ist umgezogen.",
969
+ home: "Zur Startseite",
970
+ app: "Zurück zur App",
971
+ },
972
+ acceptInvitation: {
973
+ title: "Einladung annehmen",
974
+ // Not keksdose's "Trete bei…": first person, where this catalogue's progress
975
+ // words are impersonal ("Wird geladen…").
976
+ accepting: "Beitritt läuft…",
977
+ accepted: "Einladung angenommen.",
978
+ joined: (name) => `Sie sind ${name} beigetreten.`,
979
+ continue: "Weiter",
980
+ invalid: "Beitritt über diesen Link nicht möglich",
981
+ expired: "Diese Einladung ist abgelaufen",
982
+ noToken: "Diesem Link fehlt das Einladungs-Token.",
983
+ askAgain: "Bitten Sie die Person, die Sie eingeladen hat, um eine neue Einladung.",
984
+ signInFirst: "Melden Sie sich an, um diese Einladung anzunehmen",
985
+ signInHint: "Verwenden Sie das Konto, an das die Einladung gesendet wurde.",
986
+ signIn: "Anmelden",
987
+ register: "Registrieren",
988
+ },
989
+ companySwitcher: {
990
+ // "Firma", as kastlan's sign-up asks for the "Firmenname".
991
+ switchCompany: "Firma wechseln",
992
+ heading: "Firmen",
993
+ current: (name) => `Firma: ${name}`,
994
+ currentMark: "(aktuell)",
995
+ switching: "Firma wird gewechselt…",
996
+ },
844
997
  characterCount: {
845
998
  // "Zeichen" is the same in singular and plural.
846
999
  count: (used, max) => `${n(used)} von ${n(max)} Zeichen`,
@@ -57,6 +57,15 @@ import type { ImageGridLabels } from "../components/image-grid";
57
57
  import type { LightboxLabels } from "../components/lightbox";
58
58
  import type { WriteLockLabels } from "../components/write-lock";
59
59
  import type { LegalLabels } from "../components/legal";
60
+ import type { SignInLabels } from "../auth/sign-in-form";
61
+ import type { RegisterLabels } from "../auth/register-form";
62
+ import type { CompleteNameLabels } from "../auth/complete-name-dialog";
63
+ import type { ForgotPasswordLabels } from "../auth/forgot-password-form";
64
+ import type { ResetPasswordLabels } from "../auth/reset-password-form";
65
+ import type { VerifyEmailLabels } from "../auth/verify-email";
66
+ import type { NotFoundLabels } from "../auth/not-found-page";
67
+ import type { AcceptInvitationLabels } from "../auth/accept-invitation";
68
+ import type { CompanySwitcherLabels } from "../shell/company-switcher";
60
69
  import type { AccountStateLabels } from "../components/account-chips";
61
70
  import type { ShareCardLabels } from "../components/share-card";
62
71
  import type { ReauthDialogLabels } from "../components/reauth-dialog";
@@ -323,6 +332,24 @@ export interface UiKitLabels {
323
332
  * sections that are the same in every app, filled in with the app's operator
324
333
  * (docs/legal-harmonization.md §4.3/§4.4). */
325
334
  legal: LegalLabels;
335
+ /** 0.29.0: sign-in form: credentials, passkey, 2FA, forced new password (docs/auth-harmonization.md §5). */
336
+ signIn: SignInLabels;
337
+ /** 0.29.0: the sign-up form, with the +app address tag (§4.1, §4.5). */
338
+ register: RegisterLabels;
339
+ /** 0.29.0: asking a migrated account for first and last name once (§3.3). */
340
+ completeName: CompleteNameLabels;
341
+ /** 0.29.0: requesting a reset link (always the same answer). */
342
+ forgotPassword: ForgotPasswordLabels;
343
+ /** 0.29.0: choosing a new password from a reset link; not a sign-in (§6.3). */
344
+ resetPassword: ResetPasswordLabels;
345
+ /** 0.29.0: the verify-email page and the unverified banner. */
346
+ verifyEmail: VerifyEmailLabels;
347
+ /** 0.29.0: the 404 page. */
348
+ notFound: NotFoundLabels;
349
+ /** 0.29.0: accepting an invitation with an account (§4.4). */
350
+ acceptInvitation: AcceptInvitationLabels;
351
+ /** 0.29.0: kastlan's switcher between a user's companies (§5.3). */
352
+ companySwitcher: CompanySwitcherLabels;
326
353
  /** 0.22.0: the screen-reader words of `Input` / `Textarea`'s `showCount` counter. */
327
354
  characterCount: CharacterCountLabels;
328
355
  /** 0.22.0: `CountrySelect`. Its list's "no results" and counts are `combobox`'s. */