Hi everyone,
I’m trying to use the GraphQL API to automatically translate a product once the default language has been updated. For this I’m using an automation software with a Shopify webhook. Every step seems to go well until I attempt to upload the translation to the right locale. In this instance, I’m pulling the default translatable to translate this and upload this to the en-DE locale.
Whenever I’m trying to upload it to en-DE or any other country specific local such as nl-BE it doesn’t recognize these as an existing locale. However, all of these locales are enabled in Shopify.
When I try to check all available Locales with GraphQL it also only shows EN, NL as available locales and not any en-DE or nl-BE locales. Enabling it through GraphQL also doesn’t seem to do anything.
I’ve also tried to add the locales in the theme locale files with the following names: en-DE.schema.json and en-DE.json.
I’ve used the following documentations and ChatGPT to get this far:
The code below is the code that also tries to check and enable the locales.
This is the code I’ve used, this is also made with ChatGPT in javascript, so the code might be spaghetti-ish:
try {
let inputConfig = input.config();
let shopifyApiKey = inputConfig.shopifyApiKey; // Shopify Admin API Key
let shopifyPassword = inputConfig.shopifyPassword; // Shopify Admin API Password (Access Token)
let shopifyStoreUrl = inputConfig.shopifyStoreUrl; // Your Shopify store URL
let productId = inputConfig.productId; // Hardcoded Shopify product ID for testing
let prompt = inputConfig.promptField; // Prompt for generating the translation
let openAiApiKey = inputConfig.apiKey; // OpenAI API Key
let modelName = inputConfig.modelName; // ChatGPT Model (e.g., gpt-4)
// Validate required inputs
if (!shopifyApiKey || !shopifyPassword || !shopifyStoreUrl || !prompt || !openAiApiKey || !modelName) {
throw new Error("Missing required input variables. Ensure all necessary variables are provided.");
}
// Manual Base64 Encoding Function
function toBase64(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = String(input);
let output = '';
for (let block = 0, charCode, i = 0, map = chars;
str.charAt(i | 0) || (map = '=', i % 1);
output += map.charAt(63 & (block >> (8 - (i % 1) * 8)))
) {
charCode = str.charCodeAt(i += 3 / 4);
if (charCode > 0xFF) {
throw new Error('Invalid character in input string');
}
block = (block << 8) | charCode;
}
return output;
}
let encodedCredentials = toBase64(`${shopifyApiKey}:${shopifyPassword}`);
// Step 1: Check available locales using shopLocales
console.log("Fetching available locales...");
const checkLocalesQuery = `
{
shopLocales {
locale
name
published
}
}
`;
let checkLocalesResponse = await fetch(`https://${shopifyStoreUrl}/admin/api/2023-10/graphql.json`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${encodedCredentials}`
},
body: JSON.stringify({ query: checkLocalesQuery })
});
const shopLocalesJson = await checkLocalesResponse.json();
console.log("Raw response from shopLocales query:", shopLocalesJson);
if (shopLocalesJson.errors) {
console.error("GraphQL errors:", shopLocalesJson.errors);
throw new Error("Failed to fetch available locales due to GraphQL errors.");
}
if (!shopLocalesJson.data || !shopLocalesJson.data.shopLocales) {
throw new Error("shopLocales data is missing in the API response.");
}
const availableLocales = shopLocalesJson.data.shopLocales
.filter(locale => locale.published) // Only consider published locales
.map(locale => locale.locale);
console.log("Available locales:", availableLocales);
const requiredLocale = "en-DE"; // Test locale (English for Germany)
if (!availableLocales.includes(requiredLocale)) {
console.log(`Locale "${requiredLocale}" is not enabled. Enabling it now...`);
// Step 2: Enable the required locale
const enableLocaleQuery = `
mutation enableLocale($locale: String!) {
shopLocaleEnable(locale: $locale) {
shopLocale {
locale
name
published
}
userErrors {
message
field
}
}
}
`;
let enableLocaleResponse = await fetch(`https://${shopifyStoreUrl}/admin/api/2023-10/graphql.json`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${encodedCredentials}`
},
body: JSON.stringify({
query: enableLocaleQuery,
variables: { locale: requiredLocale }
})
});
const enableLocaleJson = await enableLocaleResponse.json();
if (enableLocaleJson.errors || enableLocaleJson.data.shopLocaleEnable.userErrors.length > 0) {
console.error("Failed to enable locale:", enableLocaleJson);
throw new Error("Could not enable locale on Shopify.");
}
console.log(`Locale "${requiredLocale}" is now enabled.`);
}
// Step 3: Fetch Translatable Resource for the Product
console.log("Fetching translatable resource for the product...");
let fetchResourceQuery = {
query: `
{
translatableResources(first: 1, resourceType: PRODUCT) {
edges {
node {
resourceId
translatableContent {
key
value
digest
locale
}
}
}
}
}
`
};
let fetchResourceResponse = await fetch(
`https://${shopifyStoreUrl}/admin/api/2023-10/graphql.json`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${encodedCredentials}`
},
body: JSON.stringify(fetchResourceQuery)
}
);
if (!fetchResourceResponse.ok) {
throw new Error(
`Failed to fetch translatable resource: ${fetchResourceResponse.status} ${fetchResourceResponse.statusText}`
);
}
let fetchResourceJson = await fetchResourceResponse.json();
if (fetchResourceJson.errors) {
console.error("GraphQL errors:", fetchResourceJson.errors);
throw new Error("Failed to fetch translatable resource due to GraphQL errors.");
}
let resourceEdges = fetchResourceJson.data.translatableResources.edges;
if (resourceEdges.length === 0) {
throw new Error("No translatable resource found for the product.");
}
let translatableContent = resourceEdges[0].node.translatableContent;
console.log("Translatable content retrieved:", translatableContent);
// Extract details for `body_html`
let bodyHtmlTranslation = translatableContent.find((content) => content.key === "body_html");
if (!bodyHtmlTranslation) {
throw new Error("No translatable description (body_html) content found for the product.");
}
let translatableDigest = bodyHtmlTranslation.digest;
console.log("Digest for description translation:", translatableDigest);
// Step 4: Generate Translation using ChatGPT
console.log("Sending prompt to ChatGPT...");
let chatGptResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${openAiApiKey}`
},
body: JSON.stringify({
model: modelName,
messages: [
{ role: "system", content: "You are a helpful assistant. Translate the following into English (Germany):" },
{ role: "user", content: bodyHtmlTranslation.value }
],
})
});
if (!chatGptResponse.ok) {
let errorDetails = await chatGptResponse.text();
throw new Error(`ChatGPT API returned an error: ${chatGptResponse.status} ${chatGptResponse.statusText} - ${errorDetails}`);
}
let gptResponseJson = await chatGptResponse.json();
let translatedText = gptResponseJson.choices?.[0]?.message?.content?.trim();
if (!translatedText) {
throw new Error("No translation received from ChatGPT.");
}
console.log("Translation generated by ChatGPT:", translatedText);
// Step 5: Register Translation in Shopify using GraphQL Admin API
console.log("Uploading description translation to Shopify...");
const graphqlQuery = `
mutation CreateTranslation($id: ID!, $translations: [TranslationInput!]!) {
translationsRegister(resourceId: $id, translations: $translations) {
userErrors {
message
field
}
translations {
locale
key
value
}
}
}
`;
const variables = {
id: `gid://shopify/Product/${productId}`,
translations: [
{
key: "body_html",
value: translatedText,
locale: requiredLocale,
translatableContentDigest: translatableDigest
}
]
};
let shopifyResponse = await fetch(`https://${shopifyStoreUrl}/admin/api/2023-10/graphql.json`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${encodedCredentials}` // Basic Auth Header
},
body: JSON.stringify({ query: graphqlQuery, variables })
});
if (!shopifyResponse.ok) {
let errorDetails = await shopifyResponse.text();
throw new Error(`Shopify GraphQL API returned an error: ${shopifyResponse.status} ${shopifyResponse.statusText} - ${errorDetails}`);
}
let shopifyResponseJson = await shopifyResponse.json();
if (shopifyResponseJson.errors || shopifyResponseJson.data.translationsRegister.userErrors.length > 0) {
console.error(
"GraphQL errors:",
shopifyResponseJson.errors || shopifyResponseJson.data.translationsRegister.userErrors
);
throw new Error("Failed to register translation in Shopify.");
}
console.log("Translation successfully uploaded to Shopify:", shopifyResponseJson.data.translationsRegister.translations);
// Output success message
console.log("Process completed successfully!");
} catch (error) {
// Catch and log errors
console.error(“Error:”, error.message);
// Optional: Add detailed logging for debugging purposes
if (error.stack) {
console.error("Stack Trace:", error.stack);
}
}
Any help would be greatly appreciated, thanks in advance!