Shimpz

Write request copy once, in English, for every interface language

Every title, description, label, placeholder, and option a human request shows is an English message written with shimpz.text. Shimpz translates each distinct message once and Team shows it in the language the person chose for the Admin interface.

Write the template, then type every value it inserts

actions/publish_changes.py python
from typing import Annotated, TypedDict

from shimpz import Context, action, domain, identifier, integer, text

Zone = Annotated[str, "Zone to publish.", {"maxLength": 60, "pattern": "^[a-z0-9.-]+$"}]


class PublishedChanges(TypedDict):
    change_id: str


@action(
    human_requests=["approval"],
    description="Publish reviewed DNS changes to one of your zones.",
)
async def run(zone: Zone, change_id: str, count: int, *, ctx: Context) -> PublishedChanges:
    ctx.request_approval(
        title=text("Publish the reviewed DNS changes"),
        description=text(
            "DNS changes to publish: {count}. Zone: {zone}. Change: {change}.",
            count=integer(count, digits=4),
            zone=domain(zone, max_length=60),
            change=identifier(change_id, max_length=40),
        ),
    )
    return await publish_changes(zone, change_id)

A plain string is refused wherever request copy is expected. The template is the message: its exact text is hashed, translated once, and reused by every Assistant that writes the same English. The final provider helper is illustrative Creator code.

One literal English sentence per meaning

  • Write the template as an English, NFC-normalized string literal directly inside the call. Adjacent string literals in the call form one literal.
  • A placeholder is a {name} field whose name matches [a-z][a-z0-9_]{0,31}. Use each placeholder once, supply exactly one keyword parameter for each, and use at most 8.
  • Attribute or index access, conversions, format specifications, nested or positional fields, and literal braces are refused, as is a combining mark directly after a placeholder.
  • There is no plural or context syntax. Write count-neutral, self-explanatory copy such as Records to delete: {count}.
  • Prose that varies, such as a mode or an outcome, needs one separate message per variant.

A parameter is a bounded value, never prose

integer(value, digits=N)
A non-negative integer of at most N decimal digits, where N is at most 15.
domain(value, max_length=N)
A lowercase DNS hostname of at least two labels, using letters, digits, and inner hyphens. max_length defaults to and may not exceed 253.
dns_name(value, max_length=N)
An exact DNS record name, such as _acme-challenge.example.com or _dmarc: one or more dot-separated lowercase labels of letters, digits, _, and -, each 1 to 63 characters that neither start nor end with -. A trailing dot and a * wildcard label are refused, so a person always sees the exact name being authorized. max_length defaults to and may not exceed 253.
identifier(value, max_length=N)
An opaque value that starts with a letter or digit and continues with letters, digits, ., _, :, or -, of at most N characters, where N is at most 128.

Write each helper directly as a text() keyword argument with a literal maximum. Shimpz inserts the value exactly once, never translates or interprets it, and never sends it to the translator. A value outside its kind's form or maximum fails the request, so constrain the Action input to the parameter's form or choose a separate message. For example, show a DNS record name that may start with an underscore as a dns_name, because it is neither a domain nor an identifier.

Every message fits its field before it runs

The template's literal characters plus the declared maximum of every parameter must fit the field: 80 characters for a title, label, or option label, 120 for a placeholder, 160 for an option description, and 500 for a description. The rendered copy is checked again after insertion and is never truncated.

A text() call written directly as a request argument takes that field's bound. A call anywhere else needs a literal max_length of 80, 120, 160, or 500, and can then be used only in fields at least that large:

Inside a declared Action python
# Outside a request argument, a message names the smallest field it must fit.
if proxied:
    mode = text("Serve {zone} through the proxy.", zone=domain(zone, max_length=60), max_length=500)
else:
    mode = text("Serve {zone} as DNS only.", zone=domain(zone, max_length=60), max_length=500)

ctx.request_approval(title=text("Change the DNS mode"), description=mode)

A message used in several fields must fit the smallest of them. The text shown on the Assistant's page joins the same catalog as parameterless messages, so none of it can contain braces: the manifest summary with an 80-character bound, the manifest description and each Stored Input description with a 500-character bound, and each Action description and each Stored Input label with a 120-character bound. Their English text stays within 80, 400, 400, 80, and 80 characters, so every translation fits.

Shimpz reads the catalog without running your code

Before any Assistant code is imported, the SDK reads every actions/*.py file and every lib/**/*.py file and collects each text() call. Import the names with from shimpz import text, integer, domain, dns_name, identifier or call shimpz.text(...) after import shimpz. These forms fail with a file:line diagnostic:

Refused forms python
title = text(f"Delete {name}")             # f-string
title = text("Delete " + name)             # computed template
title = text(TEMPLATE)                     # template is not a literal in the call
say = text                                 # alias or reference
title = text("Delete {name}", **params)    # **params
title = text("Delete {name}", name=identifier(name, max_length=limit))  # non-literal maximum
name = identifier(record, max_length=40)   # helper outside a text() argument
from shimpz import *                       # wildcard import

Importing text under another name or from another module, calling it through import shimpz as ..., and a local name that hides an imported text, integer, domain, dns_name, or identifier are refused as well. Rename the local variable instead.

Eight interface languages from one English catalog

English is the catalog. Shimpz translates each message into Arabic (ar), German (de), Spanish (es), French (fr), Japanese (ja), Portuguese (pt), and Chinese (zh), and keeps the result for that exact message, so a later preparation or publication that reuses the message does not translate it again. A translation is admitted only when it keeps exactly the template's placeholders and fits the message's bound; there is no human review queue, so an automatic translation can still be imperfect.

Request kinds, option values, parameters, Assistant and Action names, identifiers, URLs, scopes, schema values, and Genesis are never translated, and neither is a Stored Input help_url.

You own the source; Shimpz owns the catalog and the pack

Message catalog
The generated contract lists every message as {id, msgid, max_length, params}, where id is the SHA-256 of the message's UTF-8 bytes. It is derived on every check, run, stage, and build.
Language pack
For a Local snapshot, shimpz assistant stage makes it and places it in the final image. For a publication, the Developers build does. It is never part of the source package.

Do not write, commit, or edit a catalog, contract, or pack in the Assistant repository. Change the text() calls instead; Test in Local shows how staging translates them.

The request is the same in every language

Each copy field travels as a reference, {"message": id, "params": {...}}. Its fingerprint covers the references and parameter values, never the display language. shimpz assistant run shows the English rendering in your terminal; Team renders the person's interface language.