@terpjs/spec 0.27.0 → 0.29.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/VERSION +1 -1
- package/catalog/backend/operations_reference_catalog.json +19 -0
- package/catalog/backend/routes_declare_operation.json +19 -0
- package/catalog/frontend/locale-catalogs-complete.json +26 -0
- package/catalog/frontend/no-untranslated-ui.json +21 -0
- package/corpus/RESIDUALS.json +8 -0
- package/corpus/backend/operations_reference_catalog/compliant-01/modules/notes/router.py +16 -0
- package/corpus/backend/operations_reference_catalog/compliant-02/modules/notes/router.py +10 -0
- package/corpus/backend/operations_reference_catalog/violation-01/modules/notes/router.py +13 -0
- package/corpus/backend/operations_reference_catalog/violation-02/modules/notes/router.py +9 -0
- package/corpus/backend/routes_declare_operation/compliant-01/control_plane/operations.py +7 -0
- package/corpus/backend/routes_declare_operation/compliant-01/modules/notes/router.py +8 -0
- package/corpus/backend/routes_declare_operation/compliant-02/modules/notes/router.py +5 -0
- package/corpus/backend/routes_declare_operation/compliant-03/control_plane/operations.py +12 -0
- package/corpus/backend/routes_declare_operation/compliant-03/modules/notes/router.py +20 -0
- package/corpus/backend/routes_declare_operation/violation-01/control_plane/operations.py +3 -0
- package/corpus/backend/routes_declare_operation/violation-01/modules/notes/router.py +3 -0
- package/corpus/backend/routes_declare_operation/violation-02/control_plane/operations.py +3 -0
- package/corpus/backend/routes_declare_operation/violation-02/modules/notes/router.py +8 -0
- package/corpus/frontend/escape-hatch/compliant-01/i18n.json +1 -0
- package/corpus/frontend/escape-hatch/compliant-01/src/modules/widgets/Widget.tsx +3 -1
- package/corpus/frontend/generated-client-only/compliant-04/i18n.json +1 -0
- package/corpus/frontend/generated-client-only/compliant-04/src/modules/widgets/Widget.tsx +6 -2
- package/corpus/frontend/layout-contract/compliant-01/i18n.json +1 -0
- package/corpus/frontend/layout-contract/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/locale-catalogs-complete/compliant-01/i18n.json +11 -0
- package/corpus/frontend/locale-catalogs-complete/compliant-01/src/modules/widgets/Widget.tsx +5 -0
- package/corpus/frontend/locale-catalogs-complete/violation-01/i18n.json +7 -0
- package/corpus/frontend/locale-catalogs-complete/violation-01/src/modules/widgets/Widget.tsx +5 -0
- package/corpus/frontend/locale-catalogs-complete/violation-02/src/modules/widgets/Widget.tsx +5 -0
- package/corpus/frontend/locale-catalogs-complete/violation-03/i18n.json +1 -0
- package/corpus/frontend/locale-catalogs-complete/violation-03/src/modules/widgets/Widget.tsx +1 -0
- package/corpus/frontend/locale-catalogs-complete/violation-04/i18n.json +1 -0
- package/corpus/frontend/locale-catalogs-complete/violation-04/src/modules/widgets/Widget.tsx +5 -0
- package/corpus/frontend/no-cross-module-imports/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-cross-module-imports/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-deep-imports/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-deep-imports/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-dom-html-injection/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-dom-html-injection/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-eval/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-eval/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-inline-styling/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-inline-styling/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-style-imports/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-style-imports/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/no-unsafe-href/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-unsafe-href/compliant-01/src/modules/widgets/Widget.tsx +3 -1
- package/corpus/frontend/no-unsafe-target-blank/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-unsafe-target-blank/compliant-01/src/modules/widgets/Widget.tsx +3 -1
- package/corpus/frontend/no-untranslated-ui/compliant-01/i18n.json +1 -0
- package/corpus/frontend/no-untranslated-ui/compliant-01/src/modules/widgets/Widget.tsx +11 -0
- package/corpus/frontend/no-untranslated-ui/violation-01/src/modules/widgets/Widget.tsx +9 -0
- package/corpus/frontend/no-untranslated-ui/violation-02/i18n.json +1 -0
- package/corpus/frontend/no-untranslated-ui/violation-02/src/main.tsx +1 -0
- package/corpus/frontend/no-untranslated-ui/violation-03/i18n.json +1 -0
- package/corpus/frontend/no-untranslated-ui/violation-03/src/modules/widgets/Widget.tsx +5 -0
- package/corpus/frontend/no-untranslated-ui/violation-04/i18n.json +1 -0
- package/corpus/frontend/no-untranslated-ui/violation-04/src/main.tsx +10 -0
- package/corpus/frontend/no-untranslated-ui/violation-05/i18n.json +9 -0
- package/corpus/frontend/no-untranslated-ui/violation-05/src/main.tsx +7 -0
- package/corpus/frontend/router-links/compliant-01/i18n.json +1 -0
- package/corpus/frontend/router-links/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/token-styled-elements/compliant-01/i18n.json +1 -0
- package/corpus/frontend/token-styled-elements/compliant-01/src/modules/widgets/Widget.tsx +2 -2
- package/corpus/frontend/token-styled-elements/compliant-02/i18n.json +1 -0
- package/corpus/frontend/token-styled-elements/compliant-02/src/modules/widgets/Widget.tsx +3 -1
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.29.1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/operations_reference_catalog",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "A route's declared operation is a typed catalog constant, never a bare value",
|
|
5
|
+
"intent": "A route can say what it does for the person calling it, but only by citing a typed definition from one shared catalog -- never a string literal or a value built inline at the call site. Without this, a route's stated purpose could drift from what the catalog documents, or two routes could describe the same action in two different words with no way to tell they were ever meant to agree.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{ "kind": "build-time", "tool": "terp.arch", "ref": "check_operations_reference_catalog" },
|
|
9
|
+
{ "kind": "runtime", "tool": "terp.core", "ref": "_validate_declared_operations" }
|
|
10
|
+
],
|
|
11
|
+
"reference": "OperationDefinition constants from the control-plane operations catalog, cited via the operation(...) route-level marker and the *_operation= keywords a canonical CRUD factory accepts.",
|
|
12
|
+
"opt_out": "# arch-allow-operations-reference-catalog: <reason>",
|
|
13
|
+
"runtime": {
|
|
14
|
+
"applicability": "required",
|
|
15
|
+
"rationale": "Boot resolves every declared operation against the registered catalog by value (id and wording together), so an unknown id or a same-id entry with different wording fails closed before the app ever serves a request."
|
|
16
|
+
},
|
|
17
|
+
"guide_topic": "operations",
|
|
18
|
+
"corpus": true
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "backend/routes_declare_operation",
|
|
3
|
+
"surface": "backend",
|
|
4
|
+
"title": "Every route declares the operation it performs",
|
|
5
|
+
"intent": "A route's method and path say how to call it; nothing about them says what it does for the person calling it. Under this app's chosen coverage level, a route that declares no operation leaves that question permanently unanswered for anyone reading the permission surface rather than the source.",
|
|
6
|
+
"layer": "static-bespoke",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{ "kind": "build-time", "tool": "terp.arch", "ref": "check_routes_declare_operation" },
|
|
9
|
+
{ "kind": "runtime", "tool": "terp.core", "ref": "_validate_declared_operations" }
|
|
10
|
+
],
|
|
11
|
+
"reference": "the operation(...) route-level marker, or for a canonical CRUD factory its *_operation= keywords, checked once the app's operations catalog opts into strict coverage.",
|
|
12
|
+
"opt_out": "# arch-allow-routes-declare-operation: <reason>",
|
|
13
|
+
"runtime": {
|
|
14
|
+
"applicability": "required",
|
|
15
|
+
"rationale": "Boot refuses a mounted route with no declared operation once the app's operations catalog opts into strict coverage; a lighter warn mode records the same gap without refusing the boot, and the coverage choice itself is optional, so an app that has not adopted it boots unchanged."
|
|
16
|
+
},
|
|
17
|
+
"guide_topic": "operations",
|
|
18
|
+
"corpus": true
|
|
19
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend/locale-catalogs-complete",
|
|
3
|
+
"surface": "frontend",
|
|
4
|
+
"title": "Every declared target locale translates every authored message",
|
|
5
|
+
"intent": "Every stable user-interface message identifier must have a non-empty translation in every declared target locale. A missing or malformed catalog, empty entries, and undocumented source-language copies fail the gate instead of silently rendering source-language text after a user switches locale.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
|
+
"ref": "terp/locale-catalogs-complete"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"kind": "runtime",
|
|
15
|
+
"tool": "@terpjs/react-core",
|
|
16
|
+
"ref": "LocaleProvider"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"reference": "Declare sourceLocale and locale message maps in frontend/i18n.json; the boundary rule and defineAppLocales validate that declaration, while LocaleProvider refuses missing target entries at render time.",
|
|
20
|
+
"opt_out": "// terp-allow-locale-catalogs-complete: <reason>",
|
|
21
|
+
"runtime": {
|
|
22
|
+
"applicability": "required",
|
|
23
|
+
"rationale": "The locale provider owns descriptor resolution and can distinguish source fallback from a missing target entry, including dynamically assembled identifiers that a static check cannot see."
|
|
24
|
+
},
|
|
25
|
+
"corpus": true
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend/no-untranslated-ui",
|
|
3
|
+
"surface": "frontend",
|
|
4
|
+
"title": "Static user-facing text participates in localization",
|
|
5
|
+
"intent": "Static user-facing text anywhere in app-authored frontend source must use the translation seam. Bare visible copy, accessibility labels, component text properties, labels stored in data, and standard toast feedback are otherwise absent from every translation catalog and silently remain in the source language.",
|
|
6
|
+
"layer": "static-portable",
|
|
7
|
+
"enforcement": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "build-time",
|
|
10
|
+
"tool": "@terpjs/eslint-boundaries",
|
|
11
|
+
"ref": "terp/no-untranslated-ui"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"reference": "Use UiText descriptors and Trans from @terpjs/react-core throughout app-authored frontend source.",
|
|
15
|
+
"opt_out": "// terp-allow-no-untranslated-ui: <reason>",
|
|
16
|
+
"runtime": {
|
|
17
|
+
"applicability": "not-applicable",
|
|
18
|
+
"rationale": "The rendered value cannot prove whether it originated from a descriptor or a bare literal, while dynamic business data legitimately remains untranslated."
|
|
19
|
+
},
|
|
20
|
+
"corpus": true
|
|
21
|
+
}
|
package/corpus/RESIDUALS.json
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
"backend/no_dynamic_sql": [
|
|
5
5
|
"an alias-renamed symbol import (`from sqlalchemy import text as sql_text`) is not required to be resolved to `text`"
|
|
6
6
|
],
|
|
7
|
+
"backend/no_manual_lease_columns": [
|
|
8
|
+
"a holder/heartbeat pair under other column names (`worker_id` + `last_seen_at`) is not required to be recognised as a hand-rolled lease: the check matches a fixed set of column spellings on a table model, not the custody shape"
|
|
9
|
+
],
|
|
10
|
+
"backend/no_manual_ownership_checks": [
|
|
11
|
+
"a jobs declaration bound to a name (`ModuleSpec(jobs=MODULE_JOBS)`) is not required to be seen as declaring background work: the build-time background-work clause matches a list or tuple LITERAL",
|
|
12
|
+
"`OwnedMixin` reached indirectly — through an intermediate base, or on a model declared in another package — is not required to be recognised as ownership by the build-time background-work clause, which matches a direct base name within the same module directory",
|
|
13
|
+
"a ModuleSpec that omits `services=` is not required to be caught by the composition-time twin, which iterates the declared services"
|
|
14
|
+
],
|
|
7
15
|
"backend/no_raw_outbound_http": [
|
|
8
16
|
"dynamic import (`importlib.import_module(\"httpx\")`) is not required to be seen as an import"
|
|
9
17
|
],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from control_plane.operations import NOTES_DELETE
|
|
2
|
+
from terp.core import operation
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@router.delete('/{note_id}', status_code=204)
|
|
6
|
+
@operation(NOTES_DELETE)
|
|
7
|
+
def delete_note(note_id, session) -> None:
|
|
8
|
+
_service.delete(session, note_id)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# A route with no declared operation at all is not this rule's concern -- an
|
|
12
|
+
# absent declaration is coverage's question (backend/routes_declare_operation),
|
|
13
|
+
# not a drifted one.
|
|
14
|
+
@router.get('/{note_id}')
|
|
15
|
+
def get_note(note_id, session):
|
|
16
|
+
return _service.get(session, note_id)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from terp.core import OperationDefinition, operation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@router.delete('/{note_id}', status_code=204)
|
|
5
|
+
@operation("notes.delete")
|
|
6
|
+
def delete_note(note_id, session) -> None:
|
|
7
|
+
_service.delete(session, note_id)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@router.get('/{note_id}')
|
|
11
|
+
@operation(OperationDefinition(id="notes.get", label="View a note"))
|
|
12
|
+
def get_note(note_id, session):
|
|
13
|
+
return _service.get(session, note_id)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from terp.core import OperationCatalog, OperationCoverage, OperationDefinition
|
|
2
|
+
|
|
3
|
+
NOTES_LIST = OperationDefinition(id="notes.list", label="List every note")
|
|
4
|
+
NOTES_CREATE = OperationDefinition(id="notes.create", label="Write a new note")
|
|
5
|
+
NOTES_GET = OperationDefinition(id="notes.get", label="View a note")
|
|
6
|
+
NOTES_UPDATE = OperationDefinition(id="notes.update", label="Edit a note")
|
|
7
|
+
NOTES_DELETE = OperationDefinition(id="notes.delete", label="Delete a note")
|
|
8
|
+
|
|
9
|
+
operation_catalog = OperationCatalog(
|
|
10
|
+
operations=(NOTES_LIST, NOTES_CREATE, NOTES_GET, NOTES_UPDATE, NOTES_DELETE),
|
|
11
|
+
coverage=OperationCoverage.STRICT,
|
|
12
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from control_plane.operations import (
|
|
2
|
+
NOTES_CREATE,
|
|
3
|
+
NOTES_DELETE,
|
|
4
|
+
NOTES_GET,
|
|
5
|
+
NOTES_LIST,
|
|
6
|
+
NOTES_UPDATE,
|
|
7
|
+
)
|
|
8
|
+
from terp.core import build_crud_router
|
|
9
|
+
|
|
10
|
+
router = build_crud_router(
|
|
11
|
+
NoteService(),
|
|
12
|
+
read_schema=NoteRead,
|
|
13
|
+
create_schema=NoteCreate,
|
|
14
|
+
update_schema=NoteUpdate,
|
|
15
|
+
list_operation=NOTES_LIST,
|
|
16
|
+
create_operation=NOTES_CREATE,
|
|
17
|
+
get_operation=NOTES_GET,
|
|
18
|
+
update_operation=NOTES_UPDATE,
|
|
19
|
+
delete_operation=NOTES_DELETE,
|
|
20
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.save":"Opslaan"}}}}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Trans } from "@terpjs/react-core";
|
|
2
|
+
|
|
1
3
|
export function Widget() {
|
|
2
4
|
// terp-allow-token-styled-elements: native button needed for a browser extension host
|
|
3
|
-
return <button
|
|
5
|
+
return <button><Trans id="widget.save" message="Save" /></button>;
|
|
4
6
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"realtime.waiting":"Wachten"}}}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRealtimeChannel } from "@terpjs/react-core";
|
|
1
|
+
import { Trans, useRealtimeChannel } from "@terpjs/react-core";
|
|
2
2
|
|
|
3
3
|
type Notice = { sequence: number; text: string };
|
|
4
4
|
|
|
@@ -13,5 +13,9 @@ export function Widget() {
|
|
|
13
13
|
channel: "system.notices",
|
|
14
14
|
validate: isNotice,
|
|
15
15
|
});
|
|
16
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
<p>
|
|
18
|
+
{notices.lastMessage?.text ?? <Trans id="realtime.waiting" message="Waiting" />}
|
|
19
|
+
</p>
|
|
20
|
+
);
|
|
17
21
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"nl","locales":{"nl":{},"en":{"messages":{"notes.title":"Notes","notes.all":"All notes"}}}}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { HubCard, HubPage } from "@terpjs/react-core";
|
|
2
2
|
export function Widget() {
|
|
3
3
|
return (
|
|
4
|
-
<HubPage title="
|
|
5
|
-
<HubCard title="
|
|
4
|
+
<HubPage title={{ id: "notes.title", message: "Notities" }}>
|
|
5
|
+
<HubCard title={{ id: "notes.all", message: "Alle notities" }} to="/notes" />
|
|
6
6
|
</HubPage>
|
|
7
7
|
);
|
|
8
8
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widgets.title":""}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const widget = { id: "widget-1" };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widgets.title":"Onderdelen"}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.docs":"Documentatie"}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.docs":"Documentatie"}}}}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Trans } from "@terpjs/react-core";
|
|
2
|
+
|
|
1
3
|
export function Widget() {
|
|
2
|
-
return <a href="https://example.com" target="_blank" rel="noopener"
|
|
4
|
+
return <a href="https://example.com" target="_blank" rel="noopener"><Trans id="widget.docs" message="Docs" /></a>;
|
|
3
5
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widgets.title":"Onderdelen","widgets.empty":"Nog niets te zien."}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ssoProviders = [{ name: "sso", label: "Single sign-on" }];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.notes":"Notities"}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.ok":"OK"},"allowIdentical":["widget.ok"]}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sourceLocale":"en","locales":{"en":{},"nl":{"messages":{"widget.save":"Opslaan"}}}}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Trans } from "@terpjs/react-core";
|
|
2
|
+
|
|
1
3
|
export function Widget() {
|
|
2
4
|
// terp-allow-token-styled-elements: native button needed for a browser extension host
|
|
3
|
-
return <button
|
|
5
|
+
return <button><Trans id="widget.save" message="Save" /></button>;
|
|
4
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terpjs/spec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "The Terp Standard — stack-neutral rule catalog, violation corpus, finding format, and refused-surface declaration (ADRs 0080/0081; packaged per ADR 0082, published per ADR 0086). Data only: consumers resolve the spec root via require.resolve('@terpjs/spec/package.json').",
|
|
5
5
|
"files": [
|
|
6
6
|
"VERSION",
|