# Which words are translated — the compiler finds every text a reader sees

> You write your app's words where they belong, in ordinary strings. The compiler finds every one a reader can see — by where it goes, not by what it looks like — with its context (page, role, neighbours, placeholders), and each is shown in the reader's language. A message is identified by its source text and its role; `Translate("…", key: "…")` splits one text that needs two translations.

<!-- id: i18n-extraction · area: i18n · stability: stable · html: https://osysharp.com/reference/i18n/extraction/ -->

## Summary        {#summary}
There is nothing to wrap and no key to invent. Write `Text("Your bag is empty.")`, `Button("Check out", …)`,
`Field("Email", …)` and `throw new Exception("That code is not one of ours.")` as always: the compiler finds each text a
reader sees and shows it in the reader's language (from `locales/<language>.osy`, [A language's words — locales/<language>.osy](https://osysharp.com/reference/i18n/translations/)), falling back
to the text you wrote — never to nothing.

## Signature      {#signature}
```osy syntax
Text("…")                                  // any literal in a text position is a message
Text($"{count} items in your bag")         // an interpolation is ONE message with a hole: "{count} items in your bag"
Text("Remove " + line.Name)                // so is a + chain: "Remove {line.Name}"
throw new Exception("…");                  // an exception's message
Translate("Open", key: "shop-status-open") // the explicit form, and the one way to give a text a key
```

## Description    {#description}
**What counts is where the text goes.** A string is a message when it reaches a place a reader sees it:

- an atom's text — `Text`, a `Button`'s or `Pressable`'s content, an accessible name (`label:`), a `placeholder:`,
  an image's `alt:`; in an `[Email]` component the mail's `subject =>`, its text, its links and an `Editable`'s
  `default:`;
- a **component parameter** that reaches one — worked out through every component, so `Button("Save")`,
  `Field("Email")` and your own `SectionLabel("Orders")` are text because of what those components do with the value,
  while a `string href` is not; a text parameter's default counts too;
- a **class field** read into one — `new NavItem { Label = "Coffee" }` is text because the shell shows `item.Label`;
- a **function's return** when the function is called in one — `Text(DeliveryLine(w))` translates the words
  `DeliveryLine` puts together, including its locals;
- a component field assigned in an action and shown — `error = "Try again";`;
- **every exception's message** — which exceptions reach a screen is not decidable (any action may `catch` and show
  `ex.Message`), so all are extracted, except a message that quotes code in `backticks`: that one is addressed to the
  developer who configured something, not to the reader;
- a page's `[Title("…")]` and its `head { Title = …; Description = …; }`;
- an enum member's declared `[Label("Draft")]` (role `label`, the enum's other labels beside it) — read in the reader's
  language wherever the label is shown, a badge, a dropdown, `status.Label`. A member with no `[Label]` shows its
  NAME (`Pending`), and that name is a word to translate too when readers see the enum — when it declares a `[Label]`
  on any member, or one of the app's own entities stores it — and the name is a word rather than a CamelCase
  identifier (`SecondStep` wants a `[Label]`). An enum only code reads (`Direction.Up`) is not text;
- an entity field's display name — its `[Label("Solved at")]`, or its name in words ("Arrived at") on an entity a
  report's field picker reaches — what a report calls the field ([[Label] on a field: what a report calls it, in the reader's language](https://osysharp.com/reference/i18n/field-labels/));
- a validation attribute's own sentence — `[Required("Give it a name.")]`, `[MaxLength(80, "…")]`,
  `[Pattern(regex, "…")]`, `[Unique("…")]` … (role `validation`) — what the browser and the server say when the rule
  refuses a value. The regex and the bounds are not words.

**What is left alone:** a string that goes nowhere a reader sees (an address, a key, a CSS value, an identifier), a
value the code COMPARES with `==` (translating it would break the comparison — unless its name says it is display
text: `Label`, `Title`, `Caption` …), a text with no letter in it (`"·"`, `"×"`), anything in a test, and an entity's
data — a product's name is data, and data is translated with `[Translatable]` ([[Translatable] — an entity's own words in every language](https://osysharp.com/reference/i18n/translatable/)).

**A sentence is one message.** `$"{n} items in your bag"` and `"Remove " + name` are each one message with holes, so a
translator can move the hole where their language puts it. A hole that is itself text — `{(reminder ? ", and one
reminder" : "")}` — is a message of its own.

**Identity is the text and its role** — `button "Save"` and `text "Save"` are two messages; "Save" on two pages is one.
The place is context, not identity: moving a button does not make its label new. When one English text needs two
translations (an "Open" that is a shop's hours and an "Open" that is a button), give ONE of them a key with
`Translate("Open", key: "shop-status-open")`. `Translate("…")` also marks a text the analysis cannot see — a name a
class keeps and the code compares elsewhere.

The browser receives only its language's table, by content address, and a text's words are never markup: they are
shown as text wherever they land.

## Examples       {#examples}
```osy title="a page whose words are all found — no wrapping, no keys" test app=i18n-extraction
public sealed class Crumb {
  public string Label;
  public string To = "";
}

[Composable]
component Heading(string title, string href) {
  render { Link(href: href) { Text(title); } }
}

string Notice() => "Roasted every Tuesday.";

[Route("/bag")]
[AllowAnonymous]
[Title("Your bag")]
component BagPage() {
  int count = 3;
  string status = "";
  Crumb home = new Crumb { Label = "Home", To = "/" };
  action Save() { status = "Saved."; }
  render {
    Stack {
      Heading("Your bag", href: "/bag");          // "Your bag" — a title, through Heading's parameter; "/bag" is not text
      Text($"{count} items in your bag");         // one message, with the hole {count}
      Text(Notice());                             // a function's words
      Text(status);                               // "Saved." — assigned in the action
      Link(href: home.To) { Text(home.Label); }   // "Home" — a class field shown as text
      Button("Save", onPress: Save);
      Text(Translate("Open", key: "shop-status-open"));   // the one "Open" that needs its own translation
    }
  }
}
```

## See also       {#see-also}
- [A language's words — locales/<language>.osy](https://osysharp.com/reference/i18n/translations/) — the file the words are translated in, and what the compile checks
- [osy translate — the request for any model, and its answer read back](https://osysharp.com/reference/i18n/translate/) — `osy translate`: the request and prompt for any model, and reading its answer back
- [Which language a reader gets — and switching it](https://osysharp.com/reference/i18n/language/) — which language a reader gets, and how they switch
- [A count's plural forms — one text per CLDR category](https://osysharp.com/reference/i18n/plurals/) — a count's plural forms
- [A history in the reader's language — keep what happened, say it when it is read](https://osysharp.com/reference/i18n/history/) — a history or a timeline: keep what happened, say it when it is read
- [[Label] on a field: what a report calls it, in the reader's language](https://osysharp.com/reference/i18n/field-labels/) — `[Label]` on a field: what a report calls it, in the reader's language
