Localizing
Packages can support different languages. WebWriter can then display widgets, snippets and package metadata in a localized form.
Localizing Lit packages automatically (recommended)
WebWriter’s build tool @webwriter/build and the preconfigured base class LitElementWw from @webwriter/lit comes with support for @lit/localize. This is especially useful if you are dealing with sub-components in your widgets, or need to translate expressions that include variables.
Step 0 (Optional): Create a free DeepL account with an API key
Using the DeepL API, we can machine translate our whole widget! While this is optional, it is highly recommended as a starting point. For this, you need a DeepL API Key:
- Create a Free (or Pro) account at https://deepl.com
 - Head to Your account and create a new API key, then copy it. Steps can be found here.
 - Create a file 
.envin your project root and add the entryDEEPL_API_KEY=.... Caution: Make sure.envis in your.gitignoreso your secret key isn’t published accidentally. 
Step 1: Make package content localizable
For @webwriter/build to be able to localize the widgets, snippets, and metadata of the package, some extra steps are needed.
Make widgets localizable
LitElementWw can automatically trigger an update when the lang attribute of your widget changes, causing the widget to render with the new language. For this to work, you need to mark all translatable strings in your code with the msg function. This can be used anywhere a normal string would be used, including in templates for text or attributes.
We add some boilerplate code (see @lit/localize documentation for details). This boilerplate is included in the template and can be uncommented. It allows your widget to notify @lit/localize that the widget lang has changed, causing it to load different translations and to re-render the widget accordingly.
import {html} from "lit"
import {LitElementWw} from "@webwriter/lit"
import {customElement, property} from "lit/decorators.js"
import LOCALIZE from "../../localization/generated"
import {msg} from "@lit/localize"
@customElement("cool-widget")
export class CoolWidget extends LitElementWw {
  localize = LOCALIZE
  render() {
    return html`<span title=${msg("This is a greeting")}>${msg("Hello, world!")}</span>`
  }
}
Refer to the @lit/localize documentation for more infos.
Make snippets localizable
No further steps are needed for snippets to be translatable: All text nodes in the snippet’s HTML structure are considered for translation and translations are embedded as a <script> tag into the snippet itself later.
Make package metadata localizable
The editing config may contain localizable metadata for the package itself and package members (label, description). If such metadata is provided in English (using the _ or en key), it will be considered for localization. A special case is the standard description property, which is always considered for localization.
{
  "description": "This package has several cool features..."
  // ...
  "editingConfig": {
    ".": {
      "label": {
        "_": "COOL Package"
      }
    }
  }
}
Step 2: Localize package members
Open your terminal and run npx @webwriter/build localize. This should create a new subfolder localization containing an exchange file for each widget language (e.g. de.xlf for German). For snippets and package metadata, similar subfolders (localization/snippets, localization/pkg) are created. Also a folder localization/generated should have been generated - we expect to import that folder in step 1.
If you configured a DeepL API key in step 0, you are asked whether you want to machine translate the package members. Note that this consumes tokens and may incur costs if you use a Pro account (Free accounts may run into the token limit instead).
Finally, translations from the exchange files are applied to the widgets (as @lit/localize JSON files), snippets (inlined script with JSON), and metadata (external editing config file).
Note:
- You can run 
npx @webwriter/build localizeagain to localize your package after adding more messages to translate. - No messages are overwritten (neither by the build tool nor by DeepL). This means you can manually change the translation in the 
.xlf/.xlifffiles - for example to correct faulty machine translations. If you make a correction, you can rerunnpx @webwriter/build localizeto apply your changes. 
Step 3: Rebuild with localization (widgets only)
For the widgets to update with the new localization, they need to be rebuilt. Changes in the snippets and package metadata should be detected automatically.
Localizing manually with lang
WebWriter passes the configured language to each widget using the Web Standard lang attribute. Widgets should change the displayed language based on that attribute.
Consider this example for a widget only providing a German translation:
import {html} from "lit"
import {LitElementWw} from "@webwriter/lit"
import {customElement, property} from "lit/decorators.js"
@customElement("cool-widget")
export default class CoolWidget extends LitElementWw {
  static LOCALIZED = {
    "What's your name?": {"de": "Wie heißt du?"}
  }
  // Helper function: Get localized form of `str` if available, otherwise fall back to `str`
  msg = (str: string) => CoolWidget.LOCALIZED[str]?.[this.lang] ?? str
  @property({attribute: true})
  accessor value: string
  render() {
    return html`
    <span>${this.msg("What's your name?")}</span>
    <textarea @change=${e => this.value = e.target.value}>
      ${this.value}
    </textarea>`
  }
}