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
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
Ndecimal digits, whereNis at most 15. domain(value, max_length=N)- A lowercase DNS hostname of at least two labels, using letters, digits, and inner hyphens.
max_lengthdefaults to and may not exceed 253. dns_name(value, max_length=N)- An exact DNS record name, such as
_acme-challenge.example.comor_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_lengthdefaults 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 mostNcharacters, whereNis 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:
# 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:
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 importImporting 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}, whereidis 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 stagemakes 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.