# Translations

Translation management for storefront text and multi-language support

## Get system default translations

> Returns system default translations only (no merchant overrides).\
> Use this to compare against \`GET /translations\` and identify which values are merchant customizations vs system defaults.\
> \
> \*\*Important:\*\* Also use this endpoint to look up the correct translation key before calling PUT.\
> Many keys have internal prefixes (e.g., \`\[\[WidgetV2]]\`, \`\[\[Checkout]]\`) that differ from the displayed UI text.\
> Search by value (not key) in the response to find the exact key to use in PUT requests.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/translations/defaults":{"get":{"tags":["Translations"],"summary":"Get system default translations","description":"Returns system default translations only (no merchant overrides).\nUse this to compare against `GET /translations` and identify which values are merchant customizations vs system defaults.\n\n**Important:** Also use this endpoint to look up the correct translation key before calling PUT.\nMany keys have internal prefixes (e.g., `[[WidgetV2]]`, `[[Checkout]]`) that differ from the displayed UI text.\nSearch by value (not key) in the response to find the exact key to use in PUT requests.\n","operationId":"getTranslationDefaults","responses":{"200":{"description":"System default translations for all supported locales","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","description":"System translations keyed by locale code","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}}}}]}}}}}}}}}
```

## Get all translations

> Returns translation config and text for ALL languages (primary + additional),\
> merged with system defaults. Each language contains \~300+ translation key-value pairs.\
> \
> Response includes config fields (primaryLanguage, detectMethod, additionalLanguages, embedContent)\
> and dynamic language objects (en, fr, de, etc.) with translation key-value pairs.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"TranslationConfig":{"type":"object","description":"Full translation object containing config and text for all languages.\nLanguage keys (en, fr, de...) are dynamic objects with translation key-value pairs.\nMirrors the internal admin translation system.\n","properties":{"primaryLanguage":{"type":"string","description":"Primary language code"},"detectMethod":{"type":"string","enum":["customerIp","browserLanguage","optionalStorefrontLanguage"],"description":"How to detect customer language:\n- customerIp: By IP geolocation\n- browserLanguage: By browser language setting\n- optionalStorefrontLanguage: Use Shopify storefront language\n"},"additionalLanguages":{"type":"array","description":"Additional languages with active status","items":{"type":"object","properties":{"code":{"type":"string","description":"Language code"},"active":{"type":"boolean","description":"Whether language is active for customers"}}}},"embedContent":{"type":"array","description":"Languages for embedded content (FAQs, exclusive products)","items":{"type":"string"}}},"additionalProperties":{"type":"object","description":"Dynamic language keys (en, fr, de, etc.) containing translation key-value pairs.\nEach language object has ~300+ keys mapping English source text to translated text.\n","additionalProperties":{"type":"string"}}}}},"paths":{"/rest_api/v2/translations":{"get":{"tags":["Translations"],"summary":"Get all translations","description":"Returns translation config and text for ALL languages (primary + additional),\nmerged with system defaults. Each language contains ~300+ translation key-value pairs.\n\nResponse includes config fields (primaryLanguage, detectMethod, additionalLanguages, embedContent)\nand dynamic language objects (en, fr, de, etc.) with translation key-value pairs.\n","operationId":"getTranslations","responses":{"200":{"description":"Full translation config and text","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TranslationConfig"}}}]}}}}}}}}}
```

## Update translations

> Updates translation config and text. Send the \*\*full translation object\*\* from the GET\
> response with modifications applied (not partial updates).\
> \
> \*\*⚠️ Key Lookup Warning:\*\*\
> Translation keys are NOT always the same as the displayed UI text.\
> Many keys have internal prefixes such as \`\[\[WidgetV2]]\`, \`\[\[Checkout]]\`, \`\[\[POS]]\`, etc.\
> \
> Example:\
> \- UI text: \`Ways to earn\`\
> \- Actual key: \`\[\[WidgetV2]]Ways to earn\`\
> \
> Before calling PUT to update a specific text:\
> 1\. ALWAYS call \`GET /translations/defaults\` first\
> 2\. Search by VALUE (not key) to find the correct key — filter entries where value matches the target text\
> 3\. Use the exact key found in step 2 for the PUT request\
> \
> Sending a PUT with a non-existent key will return \`success: true\` but will have \*\*no visible effect\*\* on the storefront.\
> \
> \*\*Workflow:\*\* \`GET /translations/defaults\` → find correct key by value → \`GET /translations\` → modify keys → \`PUT /translations\` with entire object.\
> \
> \*\*Behavior:\*\*\
> 1\. Diffs request vs system defaults — only stores overrides in Firestore\
> 2\. If primaryLanguage or its content changed — auto-translates ALL additional languages via Google Translate API\
> 3\. If embedContent changed — triggers background jobs for FAQs and exclusive products\
> 4\. Publishes metafield sync to Shopify\
> \
> \*\*Idempotent:\*\* GET → PUT unchanged = no side effects (diff produces empty result).\
> \
> \*\*CRITICAL — PUT REPLACES, does NOT merge:\*\*\
> Sending only a subset of keys will DELETE all other existing merchant customizations.\
> You MUST send the complete object from GET with your modifications applied.\
> \
> Data loss example:\
> \- Existing customs: \`{en: {"A": "custom A", "B": "custom B"}}\`\
> \- PUT with: \`{en: {"C": "custom C"}}\` (partial)\
> \- Result: \`{en: {"C": "custom C"}}\` — A and B are permanently lost\
> \
> Correct workflow:\
> 1\. \`GET /translations\` — get current full state (all keys)\
> 2\. \`GET /translations/defaults\` — get system defaults\
> 3\. Find the correct key by searching defaults by VALUE\
> 4\. Modify the key in the full GET response\
> 5\. \`PUT /translations\` — send the entire modified object back<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"TranslationConfig":{"type":"object","description":"Full translation object containing config and text for all languages.\nLanguage keys (en, fr, de...) are dynamic objects with translation key-value pairs.\nMirrors the internal admin translation system.\n","properties":{"primaryLanguage":{"type":"string","description":"Primary language code"},"detectMethod":{"type":"string","enum":["customerIp","browserLanguage","optionalStorefrontLanguage"],"description":"How to detect customer language:\n- customerIp: By IP geolocation\n- browserLanguage: By browser language setting\n- optionalStorefrontLanguage: Use Shopify storefront language\n"},"additionalLanguages":{"type":"array","description":"Additional languages with active status","items":{"type":"object","properties":{"code":{"type":"string","description":"Language code"},"active":{"type":"boolean","description":"Whether language is active for customers"}}}},"embedContent":{"type":"array","description":"Languages for embedded content (FAQs, exclusive products)","items":{"type":"string"}}},"additionalProperties":{"type":"object","description":"Dynamic language keys (en, fr, de, etc.) containing translation key-value pairs.\nEach language object has ~300+ keys mapping English source text to translated text.\n","additionalProperties":{"type":"string"}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/translations":{"put":{"tags":["Translations"],"summary":"Update translations","description":"Updates translation config and text. Send the **full translation object** from the GET\nresponse with modifications applied (not partial updates).\n\n**⚠️ Key Lookup Warning:**\nTranslation keys are NOT always the same as the displayed UI text.\nMany keys have internal prefixes such as `[[WidgetV2]]`, `[[Checkout]]`, `[[POS]]`, etc.\n\nExample:\n- UI text: `Ways to earn`\n- Actual key: `[[WidgetV2]]Ways to earn`\n\nBefore calling PUT to update a specific text:\n1. ALWAYS call `GET /translations/defaults` first\n2. Search by VALUE (not key) to find the correct key — filter entries where value matches the target text\n3. Use the exact key found in step 2 for the PUT request\n\nSending a PUT with a non-existent key will return `success: true` but will have **no visible effect** on the storefront.\n\n**Workflow:** `GET /translations/defaults` → find correct key by value → `GET /translations` → modify keys → `PUT /translations` with entire object.\n\n**Behavior:**\n1. Diffs request vs system defaults — only stores overrides in Firestore\n2. If primaryLanguage or its content changed — auto-translates ALL additional languages via Google Translate API\n3. If embedContent changed — triggers background jobs for FAQs and exclusive products\n4. Publishes metafield sync to Shopify\n\n**Idempotent:** GET → PUT unchanged = no side effects (diff produces empty result).\n\n**CRITICAL — PUT REPLACES, does NOT merge:**\nSending only a subset of keys will DELETE all other existing merchant customizations.\nYou MUST send the complete object from GET with your modifications applied.\n\nData loss example:\n- Existing customs: `{en: {\"A\": \"custom A\", \"B\": \"custom B\"}}`\n- PUT with: `{en: {\"C\": \"custom C\"}}` (partial)\n- Result: `{en: {\"C\": \"custom C\"}}` — A and B are permanently lost\n\nCorrect workflow:\n1. `GET /translations` — get current full state (all keys)\n2. `GET /translations/defaults` — get system defaults\n3. Find the correct key by searching defaults by VALUE\n4. Modify the key in the full GET response\n5. `PUT /translations` — send the entire modified object back\n","operationId":"updateTranslations","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslationConfig"}}}},"responses":{"200":{"description":"Save succeeded","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"updated":{"type":"boolean"}}}}}]}}}},"500":{"description":"Firestore save failed (e.g., undefined values in data)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Add a new language

> Adds a new language and auto-translates all text (\~300+ keys) from the primary language\
> using Google Translate API. Also auto-translates program titles and milestone descriptions.\
> \
> Returns the full translation object with the new language included.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"AddLanguageRequest":{"type":"object","required":["primaryLanguage","additionalLanguage"],"properties":{"primaryLanguage":{"type":"string","description":"Source language code to translate from"},"additionalLanguage":{"type":"string","description":"Target language code to add"}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object"},"meta":{"type":"object","description":"Additional metadata such as counts and pagination"},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"TranslationConfig":{"type":"object","description":"Full translation object containing config and text for all languages.\nLanguage keys (en, fr, de...) are dynamic objects with translation key-value pairs.\nMirrors the internal admin translation system.\n","properties":{"primaryLanguage":{"type":"string","description":"Primary language code"},"detectMethod":{"type":"string","enum":["customerIp","browserLanguage","optionalStorefrontLanguage"],"description":"How to detect customer language:\n- customerIp: By IP geolocation\n- browserLanguage: By browser language setting\n- optionalStorefrontLanguage: Use Shopify storefront language\n"},"additionalLanguages":{"type":"array","description":"Additional languages with active status","items":{"type":"object","properties":{"code":{"type":"string","description":"Language code"},"active":{"type":"boolean","description":"Whether language is active for customers"}}}},"embedContent":{"type":"array","description":"Languages for embedded content (FAQs, exclusive products)","items":{"type":"string"}}},"additionalProperties":{"type":"object","description":"Dynamic language keys (en, fr, de, etc.) containing translation key-value pairs.\nEach language object has ~300+ keys mapping English source text to translated text.\n","additionalProperties":{"type":"string"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/translations/languages":{"post":{"tags":["Translations"],"summary":"Add a new language","description":"Adds a new language and auto-translates all text (~300+ keys) from the primary language\nusing Google Translate API. Also auto-translates program titles and milestone descriptions.\n\nReturns the full translation object with the new language included.\n","operationId":"addLanguage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLanguageRequest"}}}},"responses":{"201":{"description":"Language added with auto-translated text","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TranslationConfig"}}}]}}}},"400":{"description":"Invalid or unsupported locale","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Remove a language

> Removes a language. Clears all translation data for that locale and removes it\
> from the additionalLanguages array. Publishes metafield sync after removal.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/translations/languages/{locale}":{"delete":{"tags":["Translations"],"summary":"Remove a language","description":"Removes a language. Clears all translation data for that locale and removes it\nfrom the additionalLanguages array. Publishes metafield sync after removal.\n","operationId":"removeLanguage","parameters":[{"name":"locale","in":"path","required":true,"description":"Language code to remove","schema":{"type":"string"}}],"responses":{"200":{"description":"Language removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"No translation document found for shop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Update FAQ translations

> Updates FAQ translations separately from main translations. Each language has an array\
> of FAQ items (title, content, orderBy). Triggers a background job to process FAQ\
> translation updates and sync to Shopify metafields.<br>

```json
{"openapi":"3.0.0","info":{"title":"Joy Loyalty Program - REST API v2","version":"2.0.0"},"tags":[{"name":"Translations","description":"Translation management for storefront text and multi-language support"}],"servers":[{"url":"https://dev-api.joy.so","description":"Staging"},{"url":"https://api.joy.so","description":"Production"}],"security":[{"JoyAuth":[],"JoySecretAuth":[]}],"components":{"securitySchemes":{"JoyAuth":{"type":"apiKey","in":"header","name":"X-Joy-Loyalty-App-Key"}},"schemas":{"FaqTranslationsUpdate":{"type":"object","required":["translateFaqs"],"properties":{"translateFaqs":{"type":"object","description":"FAQ translations keyed by language code","additionalProperties":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"FAQ question"},"content":{"type":"string","description":"FAQ answer"},"orderBy":{"type":"number","description":"Display order"}}}}}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"object","properties":{"message":{"type":"string"},"code":{"type":"string"},"statusCode":{"type":"integer"},"details":{"type":"object"}}},"timestamp":{"type":"string","format":"date-time"}}}}},"paths":{"/rest_api/v2/translations/faqs":{"put":{"tags":["Translations"],"summary":"Update FAQ translations","description":"Updates FAQ translations separately from main translations. Each language has an array\nof FAQ items (title, content, orderBy). Triggers a background job to process FAQ\ntranslation updates and sync to Shopify metafields.\n","operationId":"updateFaqTranslations","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaqTranslationsUpdate"}}}},"responses":{"200":{"description":"FAQ translations updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Missing translateFaqs field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```
