8.8 KiB
Label XML: Custom Labels and translation metadata shapes
This covers the two metadata XML files you author: the English base labels and the per-language translations.
Custom Labels: force-app/main/default/labels/CustomLabels.labels-meta.xml
This holds the English base labels: the source of truth for label content.
Structure
<?xml version="1.0" encoding="UTF-8"?>
<CustomLabels xmlns="http://soap.sforce.com/2006/04/metadata">
<labels>
<fullName>Welcome_Text</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Welcome banner heading</shortDescription>
<value>Welcome</value>
</labels>
<labels>
<fullName>Save_Button</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Save button label</shortDescription>
<value>Save</value>
</labels>
</CustomLabels>
Fields
| Field | Purpose | Notes |
|---|---|---|
<fullName> |
The label's API name | Used in t("Key") calls and the manifest ("c:Welcome_Text"). PascalCase, descriptive, unique. |
<language> |
Language code | Always en_US for the base label file. |
<protected> |
Managed package protection | Always false for custom labels in your org (you can edit them). |
<shortDescription> |
Internal description | For translators/developers, not shown to users. Describe what the label is for. |
<value> |
The English text | What the user sees. Can include {0}, {1} placeholders for interpolation. |
Key naming conventions
Choose keys that are:
- Descriptive:
Welcome_Textis better thanLabel1 - Context-aware:
Save_ButtonvsSave_Failed_Message(same verb, different role) - Unique: two labels shouldn't share a key even if the current text happens to match
Format: <Context>_<Role> in PascalCase with underscores between parts.
Examples:
"Welcome"→Welcome_TextorWelcome_Heading"Save"→Save_Button"Failed to save {0}: {1}"→Save_Failed_Message"Showing {0} of {1} records"→Record_Count_Display
Translations: force-app/main/default/translations/<locale>.translation-meta.xml
One file per translated language (e.g., es.translation-meta.xml for Spanish, fr.translation-meta.xml for French, ja.translation-meta.xml for Japanese).
Structure
<?xml version="1.0" encoding="UTF-8"?>
<Translations xmlns="http://soap.sforce.com/2006/04/metadata">
<customLabels>
<label>Bienvenido</label>
<name>Welcome_Text</name>
</customLabels>
<customLabels>
<label>Guardar</label>
<name>Save_Button</name>
</customLabels>
</Translations>
Fields
| Field | Purpose | Notes |
|---|---|---|
<name> |
The label's API name | Must exactly match the <fullName> in CustomLabels.labels-meta.xml. |
<label> |
The translated text | What the user sees in this language. Preserve {0}, {1} placeholders. |
File naming
The filename is <locale>.translation-meta.xml, where <locale> is the Salesforce Language code:
es.translation-meta.xml: Spanishfr.translation-meta.xml: Frenchde.translation-meta.xml: Germanja.translation-meta.xml: Japanesept_BR.translation-meta.xml: Portuguese (Brazil)zh_CN.translation-meta.xml: Chinese (Simplified)zh_TW.translation-meta.xml: Chinese (Traditional)
See Salesforce's Supported Languages for the full list.
How translations are authored
You have two options:
1. Hand-edit the XML (for small apps)
Create the <locale>.translation-meta.xml file, add one <customLabels> block per label, and type the translations directly. Good for a handful of labels or prototyping.
2. Use Translation Workbench (for scale)
Salesforce's Translation Workbench is the in-org UI where translators enter translations, which you then pull down as deployable .translation-meta.xml files.
Workflow:
- Enable Translation Workbench, Setup → Translation Workbench → Translation Settings → Enable
- Add languages, same Settings page → Add the languages you plan to support
- Enter translations, Setup → Translation Workbench → Translate → pick:
- Setup Component: Custom Label
- Language: the target language
- Label: the label to translate
- Type the translation, Save
- Retrieve as metadata, pull the translations into your project:
The CLI writes thesf project retrieve start --metadata Translations:es sf project retrieve start --metadata Translations:fr # etc., one per language.translation-meta.xmlfiles toforce-app/main/default/translations/.
Reference: Translation Workbench overview
Namespace:Key format
In the manifest (src/i18n/label-manifest.ts) and some SDK contexts, labels are written as "namespace:Key":
export const labelManifest = [
"c:Welcome_Text",
"c:Save_Button",
];
c= the custom label namespace (your org's labels)Welcome_Text= the<fullName>fromCustomLabels.labels-meta.xml
Other namespaces exist (e.g., LightningDatatable for framework-shipped labels), but most bundles only use c.
In component code, the namespace is usually implicit (set via defaultNS: "c" in the init), so you call t("Welcome_Text") not t("c:Welcome_Text").
Interpolation: {0}, {1} placeholders
Labels can include positional placeholders for runtime substitution:
<labels>
<fullName>Save_Failed_Message</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Error message when save fails</shortDescription>
<value>Failed to save {0}: {1}</value>
</labels>
At call time:
t("Save_Failed_Message", { 0: "Account", 1: "Permission denied" });
// → "Failed to save Account: Permission denied"
Translations must preserve the placeholders:
<!-- es.translation-meta.xml -->
<customLabels>
<label>Error al guardar {0}: {1}</label>
<name>Save_Failed_Message</name>
</customLabels>
The placeholders can move (Spanish grammar might flip the order), but they must stay as {0}, {1}; i18next does the substitution at render time.
See interpolation.md for how this works under the hood.
Deploy activation requirement
Before you can deploy a <locale>.translation-meta.xml file, the language must be activated in the org:
Setup → Translation Workbench → Translation Settings → Add
If you deploy a translation file for an inactive language, the deploy is rejected:
Not available for deploy for this organization
English (en_US) needs no activation; it's always available.
Language vs Locale (a common confusion)
Salesforce has two separate settings:
- Language drives translations: the text the user sees (
en_US,en_GB,de,pt_BR) - Locale drives formatting only: dates, numbers, currency (
de_DE,fr_CA)
A user can have Language = English, Locale = French: English text, French number formatting.
When you "add a language" for localization, you're working in the Language dimension. The SDK's i18n context exposes both: lang (Language) picks the translation, and locale/currency (Locale) feed Intl formatters.
To change a user's Language (to test translations), go to: Setup → My Settings → Language & Time Zone → Language → pick the language → Save.
Example: full cycle for one label in two languages
1. Add the English base label
force-app/main/default/labels/CustomLabels.labels-meta.xml:
<labels>
<fullName>Welcome_Text</fullName>
<language>en_US</language>
<protected>false</protected>
<shortDescription>Welcome banner heading</shortDescription>
<value>Welcome</value>
</labels>
2. Add the Spanish translation
force-app/main/default/translations/es.translation-meta.xml:
<customLabels>
<label>Bienvenido</label>
<name>Welcome_Text</name>
</customLabels>
3. Register in the manifest
src/i18n/label-manifest.ts:
export const labelManifest = ["c:Welcome_Text"];
4. Use in a component
import { useTranslation } from "react-i18next";
function WelcomeBanner() {
const { t } = useTranslation("c");
return <h1>{t("Welcome_Text")}</h1>;
}
5. Deploy
sf project deploy start --source-dir force-app --target-org <alias>
6. Verify
- User with Language = English sees "Welcome"
- User with Language = Spanish sees "Bienvenido"
Related
- i18n-setup.md: the init file + manifest wiring
- interpolation.md: how
{0}/{1}substitution works - verifying.md: the serve/verify flow
- gotchas.md: silent-fail traps