@eifi1/ui-kit 0.17.0 → 0.19.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/README.md +83 -4
- package/dist/chart.d.ts +13 -1
- package/dist/components/account-chips.d.ts +153 -0
- package/dist/components/account-chips.js +135 -0
- package/dist/components/account-chips.js.map +1 -0
- package/dist/components/account-settings-labels.d.ts +10 -2
- package/dist/components/account-settings-labels.js +3 -0
- package/dist/components/account-settings-labels.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -1
- package/dist/components/button-group.d.ts +13 -1
- package/dist/components/calculator.d.ts +13 -1
- package/dist/components/chip.d.ts +25 -1
- package/dist/components/chip.js +143 -6
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +42 -1
- package/dist/components/confirm-dialog.js +36 -5
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +13 -1
- package/dist/components/danger-confirm.d.ts +55 -4
- package/dist/components/danger-confirm.js +67 -30
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +2 -0
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/facing-pair.d.ts +13 -1
- package/dist/components/file-button.d.ts +13 -1
- package/dist/components/file-dropzone.d.ts +13 -1
- package/dist/components/floating-panel.d.ts +4 -1
- package/dist/components/floating-panel.js +15 -1
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +13 -1
- package/dist/components/form-actions.js +3 -0
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/legal.d.ts +84 -0
- package/dist/components/legal.js +55 -0
- package/dist/components/legal.js.map +1 -0
- package/dist/components/money-field.d.ts +13 -1
- package/dist/components/number-field.d.ts +14 -2
- package/dist/components/number-field.js +23 -22
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +13 -1
- package/dist/components/numpad-sheet.d.ts +13 -1
- package/dist/components/passkeys-setting.d.ts +27 -1
- package/dist/components/passkeys-setting.js +31 -5
- package/dist/components/passkeys-setting.js.map +1 -1
- package/dist/components/progress-bar.d.ts +15 -1
- package/dist/components/progress-bar.js +8 -1
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.d.ts +90 -0
- package/dist/components/reauth-dialog.js +158 -0
- package/dist/components/reauth-dialog.js.map +1 -0
- package/dist/components/series-chart.d.ts +13 -1
- package/dist/components/server-wake.d.ts +60 -0
- package/dist/components/server-wake.js +79 -0
- package/dist/components/server-wake.js.map +1 -0
- package/dist/components/settings-fields.d.ts +13 -1
- package/dist/components/share-card.d.ts +64 -0
- package/dist/components/share-card.js +350 -0
- package/dist/components/share-card.js.map +1 -0
- package/dist/components/status-dot.d.ts +2 -0
- package/dist/components/text-link.d.ts +13 -1
- package/dist/components/time-input.d.ts +13 -1
- package/dist/components/toast.d.ts +10 -1
- package/dist/components/toast.js +42 -2
- package/dist/components/toast.js.map +1 -1
- package/dist/components/toggle-group.js +5 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +56 -0
- package/dist/components/translation-review-editor.js +159 -0
- package/dist/components/translation-review-editor.js.map +1 -0
- package/dist/components/translation-review-labels.d.ts +92 -0
- package/dist/components/translation-review-labels.js +67 -0
- package/dist/components/translation-review-labels.js.map +1 -0
- package/dist/components/translation-review-parts.d.ts +97 -0
- package/dist/components/translation-review-parts.js +163 -0
- package/dist/components/translation-review-parts.js.map +1 -0
- package/dist/components/translation-review.d.ts +94 -0
- package/dist/components/translation-review.js +344 -0
- package/dist/components/translation-review.js.map +1 -0
- package/dist/components/ui.d.ts +13 -1
- package/dist/components/ui.js +43 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/user-avatar.d.ts +2 -0
- package/dist/components/write-lock.d.ts +68 -0
- package/dist/components/write-lock.js +33 -0
- package/dist/components/write-lock.js.map +1 -0
- package/dist/{data-table-CpkWUJBY.d.ts → data-table-3mi2R3so.d.ts} +19 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/hooks/use-file-drop.d.ts +13 -1
- package/dist/hooks/use-overlay-history.js +16 -0
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.d.ts +13 -1
- package/dist/i18n/defaults.js +15 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +81 -0
- package/dist/i18n/{locales/de.js → german.js} +110 -9
- package/dist/i18n/german.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +13 -1
- package/dist/i18n/kit-labels.js +5 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +243 -0
- package/dist/i18n/languages.js +70 -0
- package/dist/i18n/languages.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +26 -8
- package/dist/i18n/locales/de-CH.js +7 -3
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +13 -1
- package/dist/i18n/locales/en.js +8 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +13 -1
- package/dist/i18n/locales/es.js +127 -27
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +13 -1
- package/dist/i18n/locales/fr.js +101 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +13 -1
- package/dist/i18n/locales/hu.js +105 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +13 -1
- package/dist/i18n/locales/it.js +134 -34
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +13 -1
- package/dist/i18n/locales/zh.js +101 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +102 -0
- package/dist/i18n/review.js +158 -0
- package/dist/i18n/review.js.map +1 -0
- package/dist/i18n/swiss.d.ts +3 -3
- package/dist/i18n/swiss.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.js +97 -1
- package/dist/index.js.map +1 -1
- package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-D1J2_jyD.d.ts} +236 -9
- package/dist/lib/floating-stack.d.ts +31 -0
- package/dist/lib/floating-stack.js +42 -0
- package/dist/lib/floating-stack.js.map +1 -0
- package/dist/lib/server-wake.d.ts +146 -0
- package/dist/lib/server-wake.js +122 -0
- package/dist/lib/server-wake.js.map +1 -0
- package/dist/lib/translation-review.d.ts +232 -0
- package/dist/lib/translation-review.js +194 -0
- package/dist/lib/translation-review.js.map +1 -0
- package/dist/rhf/fields.d.ts +13 -1
- package/dist/rhf/form.d.ts +13 -1
- package/dist/rhf.d.ts +13 -1
- package/dist/search/global-search.d.ts +3 -1
- package/dist/search/global-search.js.map +1 -1
- package/dist/shell/app-shell.d.ts +13 -1
- package/dist/shell/top-bar-brand.d.ts +13 -1
- package/dist/shell/topbar-action-menu.d.ts +2 -0
- package/dist/shell.d.ts +13 -1
- package/dist/wizard/stepper-nav.d.ts +13 -1
- package/dist/wizard.d.ts +13 -1
- package/package.json +1 -1
- package/src/components/account-chips.tsx +302 -0
- package/src/components/account-settings-labels.ts +16 -2
- package/src/components/chip.tsx +239 -6
- package/src/components/confirm-dialog.tsx +82 -7
- package/src/components/danger-confirm.tsx +118 -33
- package/src/components/data-table.tsx +20 -0
- package/src/components/floating-panel.tsx +19 -1
- package/src/components/form-actions.tsx +22 -3
- package/src/components/legal.tsx +159 -0
- package/src/components/number-field.tsx +32 -26
- package/src/components/passkeys-setting.tsx +68 -7
- package/src/components/progress-bar.tsx +21 -1
- package/src/components/reauth-dialog.tsx +273 -0
- package/src/components/server-wake.tsx +156 -0
- package/src/components/share-card.tsx +615 -0
- package/src/components/toast.tsx +72 -2
- package/src/components/toggle-group.tsx +5 -2
- package/src/components/translation-review-editor.tsx +261 -0
- package/src/components/translation-review-labels.ts +152 -0
- package/src/components/translation-review-parts.tsx +269 -0
- package/src/components/translation-review.tsx +502 -0
- package/src/components/ui.tsx +89 -32
- package/src/components/write-lock.tsx +96 -0
- package/src/hooks/use-overlay-history.ts +51 -0
- package/src/i18n/defaults.ts +14 -0
- package/src/i18n/{locales/de.ts → german.ts} +120 -13
- package/src/i18n/kit-labels.tsx +29 -2
- package/src/i18n/languages.ts +285 -0
- package/src/i18n/locales/de-CH.ts +17 -8
- package/src/i18n/locales/en.ts +8 -0
- package/src/i18n/locales/es.ts +128 -26
- package/src/i18n/locales/fr.ts +103 -0
- package/src/i18n/locales/hu.ts +107 -0
- package/src/i18n/locales/it.ts +135 -33
- package/src/i18n/locales/zh.ts +102 -0
- package/src/i18n/review.ts +193 -0
- package/src/i18n/swiss.ts +3 -3
- package/src/index.ts +86 -1
- package/src/lib/floating-stack.ts +66 -0
- package/src/lib/server-wake.ts +291 -0
- package/src/lib/translation-review.ts +465 -0
- package/src/search/global-search.tsx +3 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +0 -61
- package/dist/i18n/locales/de-CH-informal.js +0 -8
- package/dist/i18n/locales/de-CH-informal.js.map +0 -1
- package/dist/i18n/locales/de-informal.d.ts +0 -82
- package/dist/i18n/locales/de-informal.js +0 -37
- package/dist/i18n/locales/de-informal.js.map +0 -1
- package/dist/i18n/locales/de.d.ts +0 -66
- package/dist/i18n/locales/de.js.map +0 -1
- package/src/i18n/locales/de-CH-informal.ts +0 -10
- package/src/i18n/locales/de-informal.ts +0 -61
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ReviewStatus } from '../lib/translation-review.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `translationReview` namespace: every word of the translation-review parts
|
|
5
|
+
* (`ReviewStatusChip`, `TranslationProgress`, `TranslationLocaleTabs`,
|
|
6
|
+
* `TranslationExportButton`, `TranslationReviewEditor`, `TranslationReviewPanel`).
|
|
7
|
+
*
|
|
8
|
+
* Its own module, like `data-table-labels.ts`: `kit-labels.tsx` and `defaults.ts` import
|
|
9
|
+
* it, and neither has to load the components to get the words. The English is keksdose's
|
|
10
|
+
* and kastlan's own (`translation_review.*`, `translations:*`), British.
|
|
11
|
+
*/
|
|
12
|
+
interface TranslationReviewLabels {
|
|
13
|
+
/** The five statuses — the chip, the filter, the progress legend. */
|
|
14
|
+
statusMissing: string;
|
|
15
|
+
statusUnreviewed: string;
|
|
16
|
+
statusChanged: string;
|
|
17
|
+
statusNeedsChange: string;
|
|
18
|
+
statusApproved: string;
|
|
19
|
+
/** Accessible name of the status filter. */
|
|
20
|
+
statusFilter: string;
|
|
21
|
+
/** The status filter's first option. */
|
|
22
|
+
all: string;
|
|
23
|
+
/** A status filter option with its count: "Unreviewed · 12". */
|
|
24
|
+
filterCount: (label: string, count: number) => string;
|
|
25
|
+
/** The source filter's label — shown only when the rows come from several sources
|
|
26
|
+
* (kastlan's screen texts and documents). */
|
|
27
|
+
source: string;
|
|
28
|
+
allSources: string;
|
|
29
|
+
/** The namespace filter's label and its "every one" option. */
|
|
30
|
+
namespace: string;
|
|
31
|
+
allNamespaces: string;
|
|
32
|
+
search: string;
|
|
33
|
+
/** The placeholder filter, with how many rows it would show. */
|
|
34
|
+
placeholdersOnly: (count: number) => string;
|
|
35
|
+
/** The chip on a row whose placeholders differ from the reference's. */
|
|
36
|
+
placeholderChip: string;
|
|
37
|
+
/** The key column, and the key's caption in the editor. */
|
|
38
|
+
key: string;
|
|
39
|
+
statusColumn: string;
|
|
40
|
+
/** No row matches the filters. */
|
|
41
|
+
empty: string;
|
|
42
|
+
/** In place of the text of a string the locale lacks. */
|
|
43
|
+
missingText: string;
|
|
44
|
+
/** The progress bar's label. */
|
|
45
|
+
progress: (approved: number, total: number) => string;
|
|
46
|
+
/** A locale tab's badge. */
|
|
47
|
+
localeProgress: (approved: number, total: number) => string;
|
|
48
|
+
/** Accessible name of the locale tabs. */
|
|
49
|
+
locales: string;
|
|
50
|
+
approve: string;
|
|
51
|
+
/** Send a string back: "Needs a change". */
|
|
52
|
+
flag: string;
|
|
53
|
+
/** "Needs a change" for a MISSING string: the suggestion is the translation to add. */
|
|
54
|
+
suggest: string;
|
|
55
|
+
/** Clear a verdict: back to unreviewed. */
|
|
56
|
+
reset: string;
|
|
57
|
+
/** The editor's way out without saving. */
|
|
58
|
+
cancel: string;
|
|
59
|
+
approveSelected: string;
|
|
60
|
+
resetSelected: string;
|
|
61
|
+
/** Selects every row the filters show — the phone's way into the bulk actions, where
|
|
62
|
+
* the table has no checkboxes. */
|
|
63
|
+
selectShown: string;
|
|
64
|
+
/** Over the old wording of a row that changed since its verdict. */
|
|
65
|
+
changedSince: string;
|
|
66
|
+
reviewedWording: string;
|
|
67
|
+
reviewedReference: string;
|
|
68
|
+
/** The placeholders differ: the reference's and the text's, each a list. */
|
|
69
|
+
placeholderMismatch: (reference: string, text: string) => string;
|
|
70
|
+
lastApproved: (name: string, date: string) => string;
|
|
71
|
+
lastFlagged: (name: string, date: string) => string;
|
|
72
|
+
/** The reviewer's name once the account is gone — what `name` above becomes. */
|
|
73
|
+
erasedReviewer: string;
|
|
74
|
+
/** The editor's wording field, opened on the current text. */
|
|
75
|
+
suggestion: string;
|
|
76
|
+
/** The same field for a missing string. */
|
|
77
|
+
translation: string;
|
|
78
|
+
note: string;
|
|
79
|
+
notePlaceholder: string;
|
|
80
|
+
/** The viewer may read this locale but not review it. */
|
|
81
|
+
readOnly: string;
|
|
82
|
+
/** The reviewer is limited to some areas — `areas` is their names, joined. */
|
|
83
|
+
scope: (areas: string) => string;
|
|
84
|
+
exportCorrections: (count: number) => string;
|
|
85
|
+
/** A callback rejected and no `formatError` says better. */
|
|
86
|
+
failed: string;
|
|
87
|
+
}
|
|
88
|
+
declare const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels;
|
|
89
|
+
/** A status's word, from the namespace's five. */
|
|
90
|
+
declare function reviewStatusLabel(labels: TranslationReviewLabels, status: ReviewStatus): string;
|
|
91
|
+
|
|
92
|
+
export { DEFAULT_TRANSLATION_REVIEW_LABELS, type TranslationReviewLabels, reviewStatusLabel };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const DEFAULT_TRANSLATION_REVIEW_LABELS = {
|
|
3
|
+
statusMissing: "Missing",
|
|
4
|
+
statusUnreviewed: "Unreviewed",
|
|
5
|
+
statusChanged: "Changed since review",
|
|
6
|
+
statusNeedsChange: "Needs a change",
|
|
7
|
+
statusApproved: "Approved",
|
|
8
|
+
statusFilter: "Status",
|
|
9
|
+
all: "All",
|
|
10
|
+
filterCount: (label, count) => `${label} \xB7 ${count}`,
|
|
11
|
+
source: "Texts",
|
|
12
|
+
allSources: "All texts",
|
|
13
|
+
namespace: "Area",
|
|
14
|
+
allNamespaces: "All areas",
|
|
15
|
+
search: "Search keys and text",
|
|
16
|
+
placeholdersOnly: (count) => `Only placeholder problems (${count})`,
|
|
17
|
+
placeholderChip: "Placeholders",
|
|
18
|
+
key: "Key",
|
|
19
|
+
statusColumn: "Status",
|
|
20
|
+
empty: "No strings match these filters.",
|
|
21
|
+
missingText: "Missing in this language",
|
|
22
|
+
progress: (approved, total) => `${approved} of ${total} approved`,
|
|
23
|
+
localeProgress: (approved, total) => `${approved}/${total}`,
|
|
24
|
+
locales: "Languages",
|
|
25
|
+
approve: "Approve",
|
|
26
|
+
flag: "Needs a change",
|
|
27
|
+
suggest: "Suggest translation",
|
|
28
|
+
reset: "Mark unreviewed",
|
|
29
|
+
cancel: "Cancel",
|
|
30
|
+
approveSelected: "Approve selected",
|
|
31
|
+
resetSelected: "Mark selected unreviewed",
|
|
32
|
+
selectShown: "Select all shown",
|
|
33
|
+
changedSince: "This string changed after it was reviewed. Please read it again.",
|
|
34
|
+
reviewedWording: "Wording when reviewed",
|
|
35
|
+
reviewedReference: "Reference when reviewed",
|
|
36
|
+
placeholderMismatch: (reference, text) => `The placeholders differ from the reference \u2014 reference: ${reference}; this text: ${text}. The app fills these in, so they must stay exactly as they are.`,
|
|
37
|
+
lastApproved: (name, date) => `Approved by ${name} on ${date}`,
|
|
38
|
+
lastFlagged: (name, date) => `Marked as needing a change by ${name} on ${date}`,
|
|
39
|
+
erasedReviewer: "a deleted account",
|
|
40
|
+
suggestion: "Better wording",
|
|
41
|
+
translation: "Translation",
|
|
42
|
+
note: "Note (optional)",
|
|
43
|
+
notePlaceholder: "What is wrong, or what to keep in mind",
|
|
44
|
+
readOnly: "You can read this language but not review it.",
|
|
45
|
+
scope: (areas) => `Your review is limited to: ${areas}.`,
|
|
46
|
+
exportCorrections: (count) => `Export corrections (${count})`,
|
|
47
|
+
failed: "That did not work. Please try again."
|
|
48
|
+
};
|
|
49
|
+
function reviewStatusLabel(labels, status) {
|
|
50
|
+
switch (status) {
|
|
51
|
+
case "missing":
|
|
52
|
+
return labels.statusMissing;
|
|
53
|
+
case "unreviewed":
|
|
54
|
+
return labels.statusUnreviewed;
|
|
55
|
+
case "changed":
|
|
56
|
+
return labels.statusChanged;
|
|
57
|
+
case "needs_change":
|
|
58
|
+
return labels.statusNeedsChange;
|
|
59
|
+
case "approved":
|
|
60
|
+
return labels.statusApproved;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
DEFAULT_TRANSLATION_REVIEW_LABELS,
|
|
65
|
+
reviewStatusLabel
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=translation-review-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/translation-review-labels.ts"],"sourcesContent":["import type { ReviewStatus } from \"../lib/translation-review\";\n\n/**\n * The `translationReview` namespace: every word of the translation-review parts\n * (`ReviewStatusChip`, `TranslationProgress`, `TranslationLocaleTabs`,\n * `TranslationExportButton`, `TranslationReviewEditor`, `TranslationReviewPanel`).\n *\n * Its own module, like `data-table-labels.ts`: `kit-labels.tsx` and `defaults.ts` import\n * it, and neither has to load the components to get the words. The English is keksdose's\n * and kastlan's own (`translation_review.*`, `translations:*`), British.\n */\nexport interface TranslationReviewLabels {\n /** The five statuses — the chip, the filter, the progress legend. */\n statusMissing: string;\n statusUnreviewed: string;\n statusChanged: string;\n statusNeedsChange: string;\n statusApproved: string;\n /** Accessible name of the status filter. */\n statusFilter: string;\n /** The status filter's first option. */\n all: string;\n /** A status filter option with its count: \"Unreviewed · 12\". */\n filterCount: (label: string, count: number) => string;\n /** The source filter's label — shown only when the rows come from several sources\n * (kastlan's screen texts and documents). */\n source: string;\n allSources: string;\n /** The namespace filter's label and its \"every one\" option. */\n namespace: string;\n allNamespaces: string;\n search: string;\n /** The placeholder filter, with how many rows it would show. */\n placeholdersOnly: (count: number) => string;\n /** The chip on a row whose placeholders differ from the reference's. */\n placeholderChip: string;\n /** The key column, and the key's caption in the editor. */\n key: string;\n statusColumn: string;\n /** No row matches the filters. */\n empty: string;\n /** In place of the text of a string the locale lacks. */\n missingText: string;\n /** The progress bar's label. */\n progress: (approved: number, total: number) => string;\n /** A locale tab's badge. */\n localeProgress: (approved: number, total: number) => string;\n /** Accessible name of the locale tabs. */\n locales: string;\n approve: string;\n /** Send a string back: \"Needs a change\". */\n flag: string;\n /** \"Needs a change\" for a MISSING string: the suggestion is the translation to add. */\n suggest: string;\n /** Clear a verdict: back to unreviewed. */\n reset: string;\n /** The editor's way out without saving. */\n cancel: string;\n approveSelected: string;\n resetSelected: string;\n /** Selects every row the filters show — the phone's way into the bulk actions, where\n * the table has no checkboxes. */\n selectShown: string;\n /** Over the old wording of a row that changed since its verdict. */\n changedSince: string;\n reviewedWording: string;\n reviewedReference: string;\n /** The placeholders differ: the reference's and the text's, each a list. */\n placeholderMismatch: (reference: string, text: string) => string;\n lastApproved: (name: string, date: string) => string;\n lastFlagged: (name: string, date: string) => string;\n /** The reviewer's name once the account is gone — what `name` above becomes. */\n erasedReviewer: string;\n /** The editor's wording field, opened on the current text. */\n suggestion: string;\n /** The same field for a missing string. */\n translation: string;\n note: string;\n notePlaceholder: string;\n /** The viewer may read this locale but not review it. */\n readOnly: string;\n /** The reviewer is limited to some areas — `areas` is their names, joined. */\n scope: (areas: string) => string;\n exportCorrections: (count: number) => string;\n /** A callback rejected and no `formatError` says better. */\n failed: string;\n}\n\nexport const DEFAULT_TRANSLATION_REVIEW_LABELS: TranslationReviewLabels = {\n statusMissing: \"Missing\",\n statusUnreviewed: \"Unreviewed\",\n statusChanged: \"Changed since review\",\n statusNeedsChange: \"Needs a change\",\n statusApproved: \"Approved\",\n statusFilter: \"Status\",\n all: \"All\",\n filterCount: (label, count) => `${label} · ${count}`,\n source: \"Texts\",\n allSources: \"All texts\",\n namespace: \"Area\",\n allNamespaces: \"All areas\",\n search: \"Search keys and text\",\n placeholdersOnly: (count) => `Only placeholder problems (${count})`,\n placeholderChip: \"Placeholders\",\n key: \"Key\",\n statusColumn: \"Status\",\n empty: \"No strings match these filters.\",\n missingText: \"Missing in this language\",\n progress: (approved, total) => `${approved} of ${total} approved`,\n localeProgress: (approved, total) => `${approved}/${total}`,\n locales: \"Languages\",\n approve: \"Approve\",\n flag: \"Needs a change\",\n suggest: \"Suggest translation\",\n reset: \"Mark unreviewed\",\n cancel: \"Cancel\",\n approveSelected: \"Approve selected\",\n resetSelected: \"Mark selected unreviewed\",\n selectShown: \"Select all shown\",\n changedSince: \"This string changed after it was reviewed. Please read it again.\",\n reviewedWording: \"Wording when reviewed\",\n reviewedReference: \"Reference when reviewed\",\n placeholderMismatch: (reference, text) =>\n `The placeholders differ from the reference — reference: ${reference}; this text: ${text}. The app fills these in, so they must stay exactly as they are.`,\n lastApproved: (name, date) => `Approved by ${name} on ${date}`,\n lastFlagged: (name, date) => `Marked as needing a change by ${name} on ${date}`,\n erasedReviewer: \"a deleted account\",\n suggestion: \"Better wording\",\n translation: \"Translation\",\n note: \"Note (optional)\",\n notePlaceholder: \"What is wrong, or what to keep in mind\",\n readOnly: \"You can read this language but not review it.\",\n scope: (areas) => `Your review is limited to: ${areas}.`,\n exportCorrections: (count) => `Export corrections (${count})`,\n failed: \"That did not work. Please try again.\",\n};\n\n/** A status's word, from the namespace's five. */\nexport function reviewStatusLabel(labels: TranslationReviewLabels, status: ReviewStatus): string {\n switch (status) {\n case \"missing\":\n return labels.statusMissing;\n case \"unreviewed\":\n return labels.statusUnreviewed;\n case \"changed\":\n return labels.statusChanged;\n case \"needs_change\":\n return labels.statusNeedsChange;\n case \"approved\":\n return labels.statusApproved;\n }\n}\n"],"mappings":";AAwFO,MAAM,oCAA6D;AAAA,EACxE,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,KAAK;AAAA,EACL,aAAa,CAAC,OAAO,UAAU,GAAG,KAAK,SAAM,KAAK;AAAA,EAClD,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,kBAAkB,CAAC,UAAU,8BAA8B,KAAK;AAAA,EAChE,iBAAiB;AAAA,EACjB,KAAK;AAAA,EACL,cAAc;AAAA,EACd,OAAO;AAAA,EACP,aAAa;AAAA,EACb,UAAU,CAAC,UAAU,UAAU,GAAG,QAAQ,OAAO,KAAK;AAAA,EACtD,gBAAgB,CAAC,UAAU,UAAU,GAAG,QAAQ,IAAI,KAAK;AAAA,EACzD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,aAAa;AAAA,EACb,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,qBAAqB,CAAC,WAAW,SAC/B,gEAA2D,SAAS,gBAAgB,IAAI;AAAA,EAC1F,cAAc,CAAC,MAAM,SAAS,eAAe,IAAI,OAAO,IAAI;AAAA,EAC5D,aAAa,CAAC,MAAM,SAAS,iCAAiC,IAAI,OAAO,IAAI;AAAA,EAC7E,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,OAAO,CAAC,UAAU,8BAA8B,KAAK;AAAA,EACrD,mBAAmB,CAAC,UAAU,uBAAuB,KAAK;AAAA,EAC1D,QAAQ;AACV;AAGO,SAAS,kBAAkB,QAAiC,QAA8B;AAC/F,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,EAClB;AACF;","names":[]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ReviewStatus, TranslationRow, TranslationCorrection, TranslationSummary } from '../lib/translation-review.js';
|
|
4
|
+
import { ChipTone, ChipSize, ChipVariant, ChipShape } from './chip.js';
|
|
5
|
+
import { TranslationReviewLabels } from './translation-review-labels.js';
|
|
6
|
+
import 'lucide-react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The small parts of the translation review, each usable on its own: the status
|
|
10
|
+
* vocabulary as a chip, one locale's progress, the locale tabs and the export. The page
|
|
11
|
+
* body that puts them together is `TranslationReviewPanel` (translation-review.tsx).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The tone of each status: nothing yet is neutral, a gap is orange (kastlan's choice — a
|
|
15
|
+
* hue, not a verdict), a change since review is amber, sent back is red, done is green.
|
|
16
|
+
*/
|
|
17
|
+
declare const REVIEW_STATUS_TONES: Readonly<Record<ReviewStatus, ChipTone>>;
|
|
18
|
+
interface ReviewStatusChipProps {
|
|
19
|
+
status: ReviewStatus;
|
|
20
|
+
/** Over {@link REVIEW_STATUS_TONES}. */
|
|
21
|
+
tone?: ChipTone;
|
|
22
|
+
/** Over the label. */
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
/** Default `sm` — a chip in a table cell. */
|
|
25
|
+
size?: ChipSize;
|
|
26
|
+
variant?: ChipVariant;
|
|
27
|
+
shape?: ChipShape;
|
|
28
|
+
caps?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Prop > `<UiKitProvider labels={{ translationReview }}>` > English. */
|
|
31
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
32
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** One row's status as a chip, in the kit's words and tones. Carries `data-status`. */
|
|
35
|
+
declare function ReviewStatusChip({ status, tone, children, size, variant, shape, caps, className, labels: labelsProp, ...rest }: ReviewStatusChipProps): react.JSX.Element;
|
|
36
|
+
interface TranslationProgressProps {
|
|
37
|
+
/** One locale's rows — or their {@link summariseRows} when the app has only counts. */
|
|
38
|
+
rows?: readonly TranslationRow[];
|
|
39
|
+
summary?: TranslationSummary;
|
|
40
|
+
/** The list under the bar naming each part with its count. Default true. */
|
|
41
|
+
legend?: boolean;
|
|
42
|
+
className?: string;
|
|
43
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* "120 of 3859 approved", and the bar split by status: approved, sent back, changed since
|
|
47
|
+
* review, missing — in the chips' colours. What is left of the track is unreviewed. The
|
|
48
|
+
* legend counts rows rather than percentages: "12 need a change" is the next hour's work,
|
|
49
|
+
* "0.3 %" is not.
|
|
50
|
+
*/
|
|
51
|
+
declare function TranslationProgress({ rows, summary: summaryProp, legend, className, labels: labelsProp, }: TranslationProgressProps): react.JSX.Element;
|
|
52
|
+
interface TranslationLocaleTab {
|
|
53
|
+
/** The locale code. */
|
|
54
|
+
value: string;
|
|
55
|
+
/** Its name, in its own language ("Français"). */
|
|
56
|
+
label: ReactNode;
|
|
57
|
+
/** The locale's rows, for the badge — built with the same `areas` as the panel's, so
|
|
58
|
+
* a scoped reviewer's tab counts what they can see. */
|
|
59
|
+
rows: readonly TranslationRow[];
|
|
60
|
+
}
|
|
61
|
+
interface TranslationLocaleTabsProps {
|
|
62
|
+
/** The locales the viewer may see, in the app's order — the server's answer, never a
|
|
63
|
+
* guess from a role name. */
|
|
64
|
+
locales: readonly TranslationLocaleTab[];
|
|
65
|
+
active: string;
|
|
66
|
+
onChange: (locale: string) => void;
|
|
67
|
+
className?: string;
|
|
68
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One tab per locale with its approved/total, wrapping on a phone. Nothing for a single
|
|
72
|
+
* locale: a strip with one tab is a heading that looks clickable.
|
|
73
|
+
*/
|
|
74
|
+
declare function TranslationLocaleTabs({ locales, active, onChange, className, labels: labelsProp }: TranslationLocaleTabsProps): react.JSX.Element | null;
|
|
75
|
+
interface TranslationExportButtonProps {
|
|
76
|
+
/** Every row the export covers — usually every locale's, not only the open one. */
|
|
77
|
+
rows: readonly TranslationRow[];
|
|
78
|
+
/** Listed in the file. Default: the locales the rows come from. */
|
|
79
|
+
locales?: readonly string[];
|
|
80
|
+
/** Default `translation-corrections-<date>.json`. */
|
|
81
|
+
fileName?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Take the export instead of downloading it — the entries, and the file as a ready
|
|
84
|
+
* JSON Blob. Left out, the kit downloads the Blob as `fileName`.
|
|
85
|
+
*/
|
|
86
|
+
onExport?: (corrections: TranslationCorrection[], file: Blob) => void;
|
|
87
|
+
className?: string;
|
|
88
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* "Export corrections (n)": every string sent back, as the JSON file keksdose and kastlan
|
|
92
|
+
* hand a developer — `{ exported_at, locales, corrections }`. Corrections are not applied
|
|
93
|
+
* on the page: the strings are source code. Disabled while there is nothing to export.
|
|
94
|
+
*/
|
|
95
|
+
declare function TranslationExportButton({ rows, locales, fileName, onExport, className, labels: labelsProp, }: TranslationExportButtonProps): react.JSX.Element;
|
|
96
|
+
|
|
97
|
+
export { REVIEW_STATUS_TONES, ReviewStatusChip, type ReviewStatusChipProps, TranslationExportButton, type TranslationExportButtonProps, type TranslationLocaleTab, TranslationLocaleTabs, type TranslationLocaleTabsProps, TranslationProgress, type TranslationProgressProps };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Download } from "lucide-react";
|
|
5
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
6
|
+
import { useKitFormat } from "../lib/format.js";
|
|
7
|
+
import { summariseRows, translationCorrections } from "../lib/translation-review.js";
|
|
8
|
+
import { Chip } from "./chip.js";
|
|
9
|
+
import { ProgressBar } from "./progress-bar.js";
|
|
10
|
+
import { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from "./translation-review-labels.js";
|
|
11
|
+
import { Button, Tabs } from "./ui.js";
|
|
12
|
+
const REVIEW_STATUS_TONES = {
|
|
13
|
+
missing: "orange",
|
|
14
|
+
unreviewed: "neutral",
|
|
15
|
+
changed: "warning",
|
|
16
|
+
needs_change: "danger",
|
|
17
|
+
approved: "success"
|
|
18
|
+
};
|
|
19
|
+
function ReviewStatusChip({
|
|
20
|
+
status,
|
|
21
|
+
tone,
|
|
22
|
+
children,
|
|
23
|
+
size = "sm",
|
|
24
|
+
variant,
|
|
25
|
+
shape,
|
|
26
|
+
caps,
|
|
27
|
+
className,
|
|
28
|
+
labels: labelsProp,
|
|
29
|
+
...rest
|
|
30
|
+
}) {
|
|
31
|
+
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
32
|
+
return /* @__PURE__ */ jsx(
|
|
33
|
+
Chip,
|
|
34
|
+
{
|
|
35
|
+
...rest,
|
|
36
|
+
"data-status": status,
|
|
37
|
+
tone: tone ?? REVIEW_STATUS_TONES[status],
|
|
38
|
+
size,
|
|
39
|
+
variant,
|
|
40
|
+
shape,
|
|
41
|
+
caps,
|
|
42
|
+
className,
|
|
43
|
+
children: children ?? reviewStatusLabel(labels, status)
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function TranslationProgress({
|
|
48
|
+
rows,
|
|
49
|
+
summary: summaryProp,
|
|
50
|
+
legend = true,
|
|
51
|
+
className,
|
|
52
|
+
labels: labelsProp
|
|
53
|
+
}) {
|
|
54
|
+
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
55
|
+
const { formatNumber } = useKitFormat();
|
|
56
|
+
const summary = useMemo(() => summaryProp ?? summariseRows(rows ?? []), [summaryProp, rows]);
|
|
57
|
+
const part = (status, tone, color) => ({
|
|
58
|
+
key: status,
|
|
59
|
+
value: summary[status],
|
|
60
|
+
tone,
|
|
61
|
+
color,
|
|
62
|
+
label: reviewStatusLabel(labels, status),
|
|
63
|
+
// A status with no rows stays in the legend ("Needs a change 0" is news too) but out
|
|
64
|
+
// of the bar, where it would only be a gap.
|
|
65
|
+
legendOnly: summary[status] === 0 ? true : void 0
|
|
66
|
+
});
|
|
67
|
+
const segments = [
|
|
68
|
+
part("approved", "success"),
|
|
69
|
+
part("needs_change", "danger"),
|
|
70
|
+
part("changed", "warning"),
|
|
71
|
+
// The chip's orange, which is a hue and not a ProgressBar tone. Only where there is
|
|
72
|
+
// a gap: an app whose locales are complete never hears of "missing".
|
|
73
|
+
...summary.missing > 0 ? [part("missing", void 0, "var(--hue-orange)")] : []
|
|
74
|
+
];
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
ProgressBar,
|
|
77
|
+
{
|
|
78
|
+
className,
|
|
79
|
+
label: labels.progress(summary.approved, summary.total),
|
|
80
|
+
max: Math.max(summary.total, 1),
|
|
81
|
+
segments,
|
|
82
|
+
legend,
|
|
83
|
+
formatValue: (value) => formatNumber(value)
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
function TranslationLocaleTabs({ locales, active, onChange, className, labels: labelsProp }) {
|
|
88
|
+
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
89
|
+
if (locales.length < 2) return null;
|
|
90
|
+
return /* @__PURE__ */ jsx(
|
|
91
|
+
Tabs,
|
|
92
|
+
{
|
|
93
|
+
"aria-label": labels.locales,
|
|
94
|
+
wrap: true,
|
|
95
|
+
className,
|
|
96
|
+
tabs: locales.map((locale) => {
|
|
97
|
+
const summary = summariseRows(locale.rows);
|
|
98
|
+
return {
|
|
99
|
+
id: locale.value,
|
|
100
|
+
label: locale.label,
|
|
101
|
+
name: typeof locale.label === "string" ? locale.label : locale.value,
|
|
102
|
+
badge: (
|
|
103
|
+
// The tab's own colour, dimmed — a fixed muted grey vanished on the selected
|
|
104
|
+
// tab's brand fill (390px browser check).
|
|
105
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs tabular-nums opacity-75", children: labels.localeProgress(summary.approved, summary.total) })
|
|
106
|
+
)
|
|
107
|
+
};
|
|
108
|
+
}),
|
|
109
|
+
active,
|
|
110
|
+
onChange
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
function TranslationExportButton({
|
|
115
|
+
rows,
|
|
116
|
+
locales,
|
|
117
|
+
fileName,
|
|
118
|
+
onExport,
|
|
119
|
+
className,
|
|
120
|
+
labels: labelsProp
|
|
121
|
+
}) {
|
|
122
|
+
const labels = useKitLabels("translationReview", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);
|
|
123
|
+
const corrections = useMemo(() => translationCorrections(rows), [rows]);
|
|
124
|
+
const run = () => {
|
|
125
|
+
const now = /* @__PURE__ */ new Date();
|
|
126
|
+
const file = new Blob(
|
|
127
|
+
[
|
|
128
|
+
JSON.stringify(
|
|
129
|
+
{
|
|
130
|
+
exported_at: now.toISOString(),
|
|
131
|
+
locales: locales ?? [...new Set(rows.map((r) => r.locale))],
|
|
132
|
+
corrections
|
|
133
|
+
},
|
|
134
|
+
null,
|
|
135
|
+
2
|
|
136
|
+
)
|
|
137
|
+
],
|
|
138
|
+
{ type: "application/json" }
|
|
139
|
+
);
|
|
140
|
+
if (onExport) {
|
|
141
|
+
onExport(corrections, file);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const url = URL.createObjectURL(file);
|
|
145
|
+
const a = document.createElement("a");
|
|
146
|
+
a.href = url;
|
|
147
|
+
a.download = fileName ?? `translation-corrections-${now.toISOString().slice(0, 10)}.json`;
|
|
148
|
+
a.click();
|
|
149
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
150
|
+
};
|
|
151
|
+
return /* @__PURE__ */ jsxs(Button, { variant: "secondary", disabled: corrections.length === 0, onClick: run, className, children: [
|
|
152
|
+
/* @__PURE__ */ jsx(Download, { className: "size-4", "aria-hidden": true }),
|
|
153
|
+
labels.exportCorrections(corrections.length)
|
|
154
|
+
] });
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
REVIEW_STATUS_TONES,
|
|
158
|
+
ReviewStatusChip,
|
|
159
|
+
TranslationExportButton,
|
|
160
|
+
TranslationLocaleTabs,
|
|
161
|
+
TranslationProgress
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=translation-review-parts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/translation-review-parts.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Download } from \"lucide-react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { useKitFormat } from \"../lib/format\";\nimport { summariseRows, translationCorrections } from \"../lib/translation-review\";\nimport type {\n ReviewStatus,\n TranslationCorrection,\n TranslationRow,\n TranslationSummary,\n} from \"../lib/translation-review\";\nimport { Chip } from \"./chip\";\nimport type { ChipShape, ChipSize, ChipTone, ChipVariant } from \"./chip\";\nimport { ProgressBar } from \"./progress-bar\";\nimport type { ProgressBarSegment } from \"./progress-bar\";\nimport { DEFAULT_TRANSLATION_REVIEW_LABELS, reviewStatusLabel } from \"./translation-review-labels\";\nimport type { TranslationReviewLabels } from \"./translation-review-labels\";\nimport { Button, Tabs } from \"./ui\";\n\n/**\n * The small parts of the translation review, each usable on its own: the status\n * vocabulary as a chip, one locale's progress, the locale tabs and the export. The page\n * body that puts them together is `TranslationReviewPanel` (translation-review.tsx).\n */\n\n// ── ReviewStatusChip ──────────────────────────────────────────────────────────\n\n/**\n * The tone of each status: nothing yet is neutral, a gap is orange (kastlan's choice — a\n * hue, not a verdict), a change since review is amber, sent back is red, done is green.\n */\nexport const REVIEW_STATUS_TONES: Readonly<Record<ReviewStatus, ChipTone>> = {\n missing: \"orange\",\n unreviewed: \"neutral\",\n changed: \"warning\",\n needs_change: \"danger\",\n approved: \"success\",\n};\n\nexport interface ReviewStatusChipProps {\n status: ReviewStatus;\n /** Over {@link REVIEW_STATUS_TONES}. */\n tone?: ChipTone;\n /** Over the label. */\n children?: ReactNode;\n /** Default `sm` — a chip in a table cell. */\n size?: ChipSize;\n variant?: ChipVariant;\n shape?: ChipShape;\n caps?: boolean;\n className?: string;\n /** Prop > `<UiKitProvider labels={{ translationReview }}>` > English. */\n labels?: Partial<TranslationReviewLabels>;\n [key: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/** One row's status as a chip, in the kit's words and tones. Carries `data-status`. */\nexport function ReviewStatusChip({\n status,\n tone,\n children,\n size = \"sm\",\n variant,\n shape,\n caps,\n className,\n labels: labelsProp,\n ...rest\n}: ReviewStatusChipProps) {\n const labels = useKitLabels(\"translationReview\", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);\n return (\n <Chip\n {...rest}\n data-status={status}\n tone={tone ?? REVIEW_STATUS_TONES[status]}\n size={size}\n variant={variant}\n shape={shape}\n caps={caps}\n className={className}\n >\n {children ?? reviewStatusLabel(labels, status)}\n </Chip>\n );\n}\n\n// ── TranslationProgress ───────────────────────────────────────────────────────\n\nexport interface TranslationProgressProps {\n /** One locale's rows — or their {@link summariseRows} when the app has only counts. */\n rows?: readonly TranslationRow[];\n summary?: TranslationSummary;\n /** The list under the bar naming each part with its count. Default true. */\n legend?: boolean;\n className?: string;\n labels?: Partial<TranslationReviewLabels>;\n}\n\n/**\n * \"120 of 3859 approved\", and the bar split by status: approved, sent back, changed since\n * review, missing — in the chips' colours. What is left of the track is unreviewed. The\n * legend counts rows rather than percentages: \"12 need a change\" is the next hour's work,\n * \"0.3 %\" is not.\n */\nexport function TranslationProgress({\n rows,\n summary: summaryProp,\n legend = true,\n className,\n labels: labelsProp,\n}: TranslationProgressProps) {\n const labels = useKitLabels(\"translationReview\", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);\n const { formatNumber } = useKitFormat();\n const summary = useMemo(() => summaryProp ?? summariseRows(rows ?? []), [summaryProp, rows]);\n const part = (status: ReviewStatus, tone: ProgressBarSegment[\"tone\"], color?: string): ProgressBarSegment => ({\n key: status,\n value: summary[status],\n tone,\n color,\n label: reviewStatusLabel(labels, status),\n // A status with no rows stays in the legend (\"Needs a change 0\" is news too) but out\n // of the bar, where it would only be a gap.\n legendOnly: summary[status] === 0 ? true : undefined,\n });\n const segments = [\n part(\"approved\", \"success\"),\n part(\"needs_change\", \"danger\"),\n part(\"changed\", \"warning\"),\n // The chip's orange, which is a hue and not a ProgressBar tone. Only where there is\n // a gap: an app whose locales are complete never hears of \"missing\".\n ...(summary.missing > 0 ? [part(\"missing\", undefined, \"var(--hue-orange)\")] : []),\n ];\n return (\n <ProgressBar\n className={className}\n label={labels.progress(summary.approved, summary.total)}\n max={Math.max(summary.total, 1)}\n segments={segments}\n legend={legend}\n formatValue={(value) => formatNumber(value)}\n />\n );\n}\n\n// ── TranslationLocaleTabs ─────────────────────────────────────────────────────\n\nexport interface TranslationLocaleTab {\n /** The locale code. */\n value: string;\n /** Its name, in its own language (\"Français\"). */\n label: ReactNode;\n /** The locale's rows, for the badge — built with the same `areas` as the panel's, so\n * a scoped reviewer's tab counts what they can see. */\n rows: readonly TranslationRow[];\n}\n\nexport interface TranslationLocaleTabsProps {\n /** The locales the viewer may see, in the app's order — the server's answer, never a\n * guess from a role name. */\n locales: readonly TranslationLocaleTab[];\n active: string;\n onChange: (locale: string) => void;\n className?: string;\n labels?: Partial<TranslationReviewLabels>;\n}\n\n/**\n * One tab per locale with its approved/total, wrapping on a phone. Nothing for a single\n * locale: a strip with one tab is a heading that looks clickable.\n */\nexport function TranslationLocaleTabs({ locales, active, onChange, className, labels: labelsProp }: TranslationLocaleTabsProps) {\n const labels = useKitLabels(\"translationReview\", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);\n if (locales.length < 2) return null;\n return (\n <Tabs\n aria-label={labels.locales}\n wrap\n className={className}\n tabs={locales.map((locale) => {\n const summary = summariseRows(locale.rows);\n return {\n id: locale.value,\n label: locale.label,\n name: typeof locale.label === \"string\" ? locale.label : locale.value,\n badge: (\n // The tab's own colour, dimmed — a fixed muted grey vanished on the selected\n // tab's brand fill (390px browser check).\n <span className=\"text-xs tabular-nums opacity-75\">\n {labels.localeProgress(summary.approved, summary.total)}\n </span>\n ),\n };\n })}\n active={active}\n onChange={onChange}\n />\n );\n}\n\n// ── TranslationExportButton ───────────────────────────────────────────────────\n\nexport interface TranslationExportButtonProps {\n /** Every row the export covers — usually every locale's, not only the open one. */\n rows: readonly TranslationRow[];\n /** Listed in the file. Default: the locales the rows come from. */\n locales?: readonly string[];\n /** Default `translation-corrections-<date>.json`. */\n fileName?: string;\n /**\n * Take the export instead of downloading it — the entries, and the file as a ready\n * JSON Blob. Left out, the kit downloads the Blob as `fileName`.\n */\n onExport?: (corrections: TranslationCorrection[], file: Blob) => void;\n className?: string;\n labels?: Partial<TranslationReviewLabels>;\n}\n\n/**\n * \"Export corrections (n)\": every string sent back, as the JSON file keksdose and kastlan\n * hand a developer — `{ exported_at, locales, corrections }`. Corrections are not applied\n * on the page: the strings are source code. Disabled while there is nothing to export.\n */\nexport function TranslationExportButton({\n rows,\n locales,\n fileName,\n onExport,\n className,\n labels: labelsProp,\n}: TranslationExportButtonProps) {\n const labels = useKitLabels(\"translationReview\", DEFAULT_TRANSLATION_REVIEW_LABELS, labelsProp);\n const corrections = useMemo(() => translationCorrections(rows), [rows]);\n const run = () => {\n const now = new Date();\n const file = new Blob(\n [\n JSON.stringify(\n {\n exported_at: now.toISOString(),\n locales: locales ?? [...new Set(rows.map((r) => r.locale))],\n corrections,\n },\n null,\n 2,\n ),\n ],\n { type: \"application/json\" },\n );\n if (onExport) {\n onExport(corrections, file);\n return;\n }\n const url = URL.createObjectURL(file);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = fileName ?? `translation-corrections-${now.toISOString().slice(0, 10)}.json`;\n a.click();\n // After the click has handed the URL to the download, not before.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n };\n return (\n <Button variant=\"secondary\" disabled={corrections.length === 0} onClick={run} className={className}>\n <Download className=\"size-4\" aria-hidden />\n {labels.exportCorrections(corrections.length)}\n </Button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { TranslationRow, TranslationReviewWrite, TranslationReviewKey, TranslationReviewFilter } from '../lib/translation-review.js';
|
|
4
|
+
import { TranslationReviewLabels } from './translation-review-labels.js';
|
|
5
|
+
export { DEFAULT_TRANSLATION_REVIEW_LABELS } from './translation-review-labels.js';
|
|
6
|
+
export { REVIEW_STATUS_TONES, ReviewStatusChip, ReviewStatusChipProps, TranslationExportButton, TranslationExportButtonProps, TranslationLocaleTab, TranslationLocaleTabs, TranslationLocaleTabsProps, TranslationProgress, TranslationProgressProps } from './translation-review-parts.js';
|
|
7
|
+
export { TranslationReviewEditor, TranslationReviewEditorProps } from './translation-review-editor.js';
|
|
8
|
+
import './chip.js';
|
|
9
|
+
import 'lucide-react';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An app's translation review page, as kit parts (H1, docs/i18n-harmonization.md):
|
|
13
|
+
* keksdose's /translations, which kastlan was about to build a second time. The app keeps
|
|
14
|
+
* what is its own — where the strings come from, which locale is read against which, who
|
|
15
|
+
* may review what, every request — and hands the kit rows (`translationRows`) and two
|
|
16
|
+
* callbacks. Everything a reviewer touches is here.
|
|
17
|
+
*
|
|
18
|
+
* Presentational — no request, no route — and one LOCALE at a time. Its table is the
|
|
19
|
+
* kit's DataTable, so like every DataTable it renders under the app's Router. The page
|
|
20
|
+
* around it puts
|
|
21
|
+
* {@link TranslationLocaleTabs} above it and {@link TranslationExportButton} in its header,
|
|
22
|
+
* because both span every locale while this panel is the one open.
|
|
23
|
+
*
|
|
24
|
+
* Callbacks may return promises, and the panel waits for them: an editor stays open with
|
|
25
|
+
* its text and the error when a save is refused (a 403 for a key outside the reviewer's
|
|
26
|
+
* areas, a network failure), a bulk action keeps its selection. Chunking a long bulk
|
|
27
|
+
* write to the server's batch limit is the app's; the panel sends one call per action.
|
|
28
|
+
*/
|
|
29
|
+
type MaybePromise = void | Promise<unknown>;
|
|
30
|
+
interface TranslationReviewPanelProps {
|
|
31
|
+
/** One locale's rows, from `translationRows`. */
|
|
32
|
+
rows: readonly TranslationRow[];
|
|
33
|
+
/** The reviewed language's own name — the text column's header ("Français"). */
|
|
34
|
+
localeLabel: string;
|
|
35
|
+
/** The reference language's own name — the reference column's header. */
|
|
36
|
+
referenceLabel: string;
|
|
37
|
+
/** Store verdicts: one for an editor or a row's approve button, many for a bulk
|
|
38
|
+
* approve. Left out (or `readOnly`): nothing can be changed. */
|
|
39
|
+
onSave?: (writes: TranslationReviewWrite[]) => MaybePromise;
|
|
40
|
+
/** Clear verdicts — back to unreviewed. Left out: no reset. */
|
|
41
|
+
onClear?: (keys: TranslationReviewKey[]) => MaybePromise;
|
|
42
|
+
/** A locale the viewer may read but not review: the rows and their verdicts, no
|
|
43
|
+
* actions, and a line saying so. */
|
|
44
|
+
readOnly?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The areas the reviewer is limited to (keksdose's lawyer: `["legal"]`), as the server
|
|
47
|
+
* answers them; `null` is every area. Rows outside are not shown or counted — build the
|
|
48
|
+
* rows with the same `areas` and they never exist — and a notice names the areas.
|
|
49
|
+
*/
|
|
50
|
+
areas?: readonly string[] | null;
|
|
51
|
+
/** An area's name for the notice ("the legal pages (Imprint, …)"). Default: the key. */
|
|
52
|
+
areaLabels?: Readonly<Record<string, string>>;
|
|
53
|
+
/** Default `keyInArea`: the key is the area or sits under it (`legal.…`, `legal:…`). */
|
|
54
|
+
inArea?: (key: string, area: string) => boolean;
|
|
55
|
+
/** A source's name for the source filter, which appears once the rows come from more
|
|
56
|
+
* than one (kastlan: `{ screen: "Screen", documents: "PDFs and emails" }`). Default:
|
|
57
|
+
* the source itself. */
|
|
58
|
+
sourceLabels?: Readonly<Record<string, string>>;
|
|
59
|
+
/**
|
|
60
|
+
* Controlled filters — for an app that keeps them in the URL (keksdose's `?status=`,
|
|
61
|
+
* `?area=`, `?q=`). Whatever is left out is the panel's own. Pair with `onFilterChange`.
|
|
62
|
+
*/
|
|
63
|
+
filter?: Partial<TranslationReviewFilter>;
|
|
64
|
+
onFilterChange?: (filter: TranslationReviewFilter) => void;
|
|
65
|
+
/** The progress bar over the filters. Default true. */
|
|
66
|
+
progress?: boolean;
|
|
67
|
+
/** Rows per page. Default 50. */
|
|
68
|
+
pageSize?: number;
|
|
69
|
+
/** Persists the table's sort and page size (DataTable's `storageKey`). */
|
|
70
|
+
storageKey?: string;
|
|
71
|
+
/** How a verdict's date reads in the editor. */
|
|
72
|
+
formatDate?: (iso: string) => string;
|
|
73
|
+
/** Turns a callback's rejection into words. Default {@link TranslationReviewLabels.failed}. */
|
|
74
|
+
formatError?: (error: unknown) => ReactNode;
|
|
75
|
+
className?: string;
|
|
76
|
+
labels?: Partial<TranslationReviewLabels>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The page body for one locale: progress, filters (status, source, namespace, search,
|
|
80
|
+
* placeholder problems), the strings with an editor that unfolds under each, and the
|
|
81
|
+
* bulk actions over a selection.
|
|
82
|
+
*
|
|
83
|
+
* The status filter is the work queue in order — what is not there, what nobody has
|
|
84
|
+
* read, what changed after it was read, what was sent back, what is done — with each
|
|
85
|
+
* count on it. "Missing" is offered only where something is.
|
|
86
|
+
*
|
|
87
|
+
* Bulk approve takes the selected rows that are not approved yet and have a text:
|
|
88
|
+
* approving an absent string says nothing, and re-approving one says it again. On a
|
|
89
|
+
* phone the table has no checkboxes, so "Select all shown" selects what the filters
|
|
90
|
+
* show — filter to `Unreviewed` in one area, read, select, approve.
|
|
91
|
+
*/
|
|
92
|
+
declare function TranslationReviewPanel({ rows: rowsProp, localeLabel, referenceLabel, onSave, onClear, readOnly, areas, areaLabels, inArea, sourceLabels, filter: filterProp, onFilterChange, progress, pageSize, storageKey, formatDate, formatError, className, labels: labelsProp, }: TranslationReviewPanelProps): react.JSX.Element;
|
|
93
|
+
|
|
94
|
+
export { TranslationReviewLabels, TranslationReviewPanel, type TranslationReviewPanelProps };
|