---
title: "Apply translations for one page and locale"
method: POST
path: "/projects/{projectId}/page-translations/{pageId}"
tags: ["Translations"]
---

# Apply translations for one page and locale

`POST /projects/{projectId}/page-translations/{pageId}`

## Request

- Base URL: `https://studio.weaverse.io/api/v1/content`
- URL: `https://studio.weaverse.io/api/v1/content/projects/{projectId}/page-translations/{pageId}`
- Auth: HTTP bearer

Writes translation overrides for an owned page and one configured, non-default target locale. Send the entries read from `GET` with `translation` replaced; `source` and `targetGeneration` must be the values `GET` returned.

- Only `Translation` rows change. Base page content, its items and every other locale are untouched.
- The project default language is rejected (400): it is the source other locales translate from.
- Each entry is compare-and-set on both its `source` and its `targetGeneration`, so a delayed agent can never overwrite a source the merchant edited or a newer translation another writer stored. Refused entries are counted in `stale` and were not written; re-read and resubmit them. `applied + stale = requested`.
- `translation: ""` clears a key, keeping the tombstone row that distinguishes "cleared" from "never translated".
- `targetGeneration` is required on every entry (`null` when `GET` returned `null`); omitting it is a 400, not a blind write.
- At most 250 entries per request; duplicate `itemId` + `key` pairs are rejected.
- A request that applies nothing advances no project revision and flushes no cache.
- If the write committed but cache invalidation failed, the response is a 500 whose message states how many entries were applied. Verify with `GET`; retrying the same entries will not flush the cache, because they then lose the target compare-and-set as no-ops.

Required scope: `content:write`

## Path parameters

- `projectId` string, required
- `pageId` string, required

## Request body

- PageTranslationsUpdateRequest
  - `locale` string, required: Target locale, resolved through the same language identity the storefront uses. Must be a configured, non-default language.
  - `translations` PageTranslationEntry[], required: Entries echoed from GET with `translation` replaced. `source` and `targetGeneration` must be the values GET returned.
    - `itemId` string, required: The item the key belongs to.
    - `key` string, required: The extraction path of the translatable field, rooted at the item data column (e.g. `data.title`). Use it verbatim; it is the stored translation key.
    - `source` string, required: The text the translation answers for, resolved server-side from the item value, its component-schema default, or the project default language overlay — whichever the runtime actually translates FROM.
    - `translation` string, nullable, required: The current target text. `""` is a deliberate clear; `null` means no row exists, or the stored one answers a source the merchant has since changed.
    - `targetGeneration` string, nullable, required: The raw stored target value at read time, independent of freshness: `null` = no row, `""` = cleared. Replay it verbatim on write; it is the compare-and-set token that stops a delayed agent overwriting a newer translation.

## Response `200`

Decision for the whole batch. `applied + stale = requested`; a non-zero `stale` means those keys must be re-read and resubmitted.

- PageTranslationsUpdateResult
  - `object` 'page_translations_update'
  - `projectId` string
  - `pageId` string
  - `language` object
    - `id` string
    - `code` string
  - `requested` integer
  - `applied` integer: Entries that passed both the source and target compare-and-set and were written.
  - `stale` integer: Entries refused because the source or the target moved since the read. Re-run GET and resubmit those keys; they were NOT written.

## Other responses

- `400`: Malformed body, unconfigured locale, the project default language, or more than 250 entries
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Project not found, page not owned by this project, or an item not on that page
- `405`: Method not allowed
- `500`: Unexpected server error. Also returned when the translations COMMITTED but the cache fan-out failed: the message says how many were applied and that they may not be readable yet. Verify with GET; retrying the same entries will not flush the cache, because they then lose the target compare-and-set as no-ops.

---

[API](https://skmtc.dev/weaverse/apis/weaverse-content-api.md) - [All operations](https://skmtc.dev/weaverse/apis/weaverse-content-api/llms.txt) - [OpenAPI document](https://skmtc.dev/weaverse/apis/weaverse-content-api/revisions/d189794169bb?raw)
