Separate dropdown options from localized attribute labels
Separate a field's translated label, stable option codes, localized option labels, and saved product selection so translation never changes identity.
In this guide
- attribute definition
- localized field label
- option code
- localized option label
- product selection
Name the three layers
For a fictional SurfaceFinish field, the definition tells the system that the field exists. Its localized label tells an editor what the field is called. Controlled options such as matte and gloss have their own stable codes and localized display values. A product then stores a selected option or value.
Changing a label should not change the stored option code. Translating an option should not rewrite a product record.
Design stable option codes
The JSON keeps the field label and option labels separate. SurfaceFinish is the field code. 'Surface finish' and 'Finition de surface' name that field for different locales. The selected product value is a stable option code such as matte; 'Matte' and 'Mat' are display labels for that same option.
Use codes for storage and comparisons. Reject duplicate codes within the field and reject an assignment to an option the field does not define. A translation change should alter what the reader sees without changing the product's selected option.
surface-finish-fixture.json json
{
"attributeCode": "SurfaceFinish",
"labels": { "en-US": "Surface finish", "fr-CA": "Finition de surface" },
"options": [
{ "code": "matte", "labels": { "en-US": "Matte", "fr-CA": "Mat" } },
{ "code": "gloss", "labels": { "en-US": "Gloss", "fr-CA": "Brillant" } }
]
}
A product storing matte displays 'Mat' in fr-CA and 'Matte' in en-US. Renaming the French label does not require a product update because the stored selection remains matte. The supplemental optionLabel function demonstrates the display lookup using this options array.
Verify fallback deliberately
Choose a missing-translation policy for the consuming application. The supplemental resolver tries the requested locale, then the supplied fallback locale, then the stable code. That sequence is defined by this helper; verify the native Admin and storefront behavior separately for the installed Znode patch.
Check each option, an empty product value, an unknown code, a missing translation, and a deliberately blank label. Confirm that fallback changes only display text and never rewrites the saved option.
Resolve a dropdown label without changing its stable option code
Pass the fixture's options array, selected stable code, requested locale, and optional fallback locale. For code matte and locale fr-CA, the result is Mat. If that label is absent or blank, the helper tries en-US by default, then returns matte. Unknown or duplicate codes raise an error. Display the returned label and continue saving the original option code.
attributes/option-label.ts typescript
export function optionLabel(
input: unknown, code: string, locale: string, fallback = "en-US"
): string {
if (!Array.isArray(input) || input.length > 100 || !/^[a-z0-9-]{1,60}$/.test(code))
throw new Error("A bounded option list and stable code are required.");
const options = new Map<string, Record<string, string>>();
for (const raw of input) {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Invalid option.");
const row = raw as Record<string, unknown>;
if (typeof row.code !== "string" || !/^[a-z0-9-]{1,60}$/.test(row.code) ||
options.has(row.code) || !row.labels || typeof row.labels !== "object" ||
Array.isArray(row.labels)) throw new Error("Invalid or duplicate option code.");
const labels: Record<string, string> = Object.create(null);
const entries = Object.entries(row.labels);
if (entries.length > 20) throw new Error("Too many locale labels.");
for (const [key, value] of entries) {
if (typeof value !== "string" || value.length > 200) throw new Error("Invalid label.");
labels[key] = value;
}
options.set(row.code, labels);
}
const labels = options.get(code);
if (!labels) throw new Error("Unknown option code.");
return labels[locale]?.trim() || labels[fallback]?.trim() || code;
}
References and further reading
Bring your next engineering question.
Need help making catalog data work across locales?
Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.