Skip to content

fix(API): make translation content and key description nullable #STRINGS-3652 - #1325

Open
Mikko Karkee (Varpuspaavi) wants to merge 5 commits into
mainfrom
STRINGS-3652-nullable-translation-update
Open

Mikko Karkee (Varpuspaavi) wants to merge 5 commits into
mainfrom
STRINGS-3652-nullable-translation-update

Conversation

@Varpuspaavi

@Varpuspaavi Mikko Karkee (Varpuspaavi) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

STRINGS-3652: phrase translations update --data '{"content":""}' returns success but leaves the translation unchanged. The same payload sent directly to the API clears it.

content on translation/update (and description on key/update) were plain type: string, so the generated Go model uses string with json:",omitempty". The CLI unmarshals --data into that struct and re-marshals it, and omitempty drops "", so the key never reaches the API.

Change

  • paths/translations/update.yaml: mark content as nullable: true; document that an empty string or null clears it and omitting it leaves it unchanged. The CLI drops null, so the description tells CLI users to send an empty string.
  • paths/keys/update.yaml: mark description as nullable: true (same omitempty bug), same wording.
  • doc/compiled.json: regenerated (make lint).

Generated Go model now uses *NullableString for these two fields (same pattern as repo_sync name). clients/ is gitignored and regenerated on release.

Why not branch and plural_suffix

An earlier revision also marked these nullable. Checking the API code showed that was wrong:

  • branch is a branch selector read from params[:branch], not a translation attribute. null, "" and omitted all fall back to the main project, so omitempty dropping "" is harmless.
  • plural_suffix is NOT NULL DEFAULT "" in the DB. null on a pluralized key gives a 422; on a non-plural key it probably violates the NOT NULL constraint (not confirmed by running it). "" is the safe "no suffix" value, and dropping it rarely matters.

API behavior of null (from reading the API code, not from running requests)

  • translation content: null clears it (stored as nil; "" is stored as an empty string; both are treated as blank). Omitted leaves it unchanged.
  • key description: null clears it (NULL; "" stores ""). Omitted leaves it unchanged.

Verification

Generated the Go client with the repo's generator config and ran a marshal round-trip on TranslationUpdateParameters:

Input Output
{"content":""} {"content":""}
{"content":"x"} {"content":"x"}
{"content":"null"} (the string) {"content":"null"}
{} {}
{"content":null} {}

make lint passes. Other language generators were not run.

Client impact (source-breaking for typed clients)

nullable: true changes the generated field type for translation/update content and key/update description:

  • phrase-go: string -> *NullableString. Existing code such as body.Content = "x" no longer compiles; callers must switch to the nullable wrapper type:
    s := "x"
    body.Content = phrase.NewNullableString(&s)
    Checked by generating the Go client from main and from this branch and compiling against the branch model.
  • Go callers can now send an explicit JSON null (previously impossible with string). NewNullableString(nil), a zero-value &NullableString{} and a NullableString after Unset() all marshal as "content":null, because MarshalJSON ignores isSet. Since null clears these fields, a forgotten or zero-valued wrapper clears the field instead of omitting it.
  • A non-nil pointer to "" is now sent (the point of this fix); before, "" was dropped by omitempty.
  • Other typed clients (Java, Swift, TypeScript, ...) get nullable field types too. Per-language generated types were not inspected.
  • oasdiff reports this only as info (request-property-became-nullable) because the wire contract is backwards compatible: any previously valid request is still valid.

Release note: the PR title is fix(API), which release-please treats as a patch bump. Because of the source-breaking Go/typed-client change, the release owners should decide whether to mark it as breaking (fix(API)!:) for a major bump, or accept a patch bump with a changelog note. This is a call for the reviewers.

Known limitation (out of scope)

{"content":null} through the CLI is still dropped: encoding/json sets a *NullableString to nil on JSON null without calling UnmarshalJSON. Fixing it needs a Go model template change affecting all nullable fields, so it is deliberately not done here. Direct API calls with null clear the field.

Follow-up

Once released, phrase-cli needs a phrase-go dependency bump to pick this up.

🤖 Generated with Claude Code

…le #STRINGS-3652

Plain string fields generate Go structs with omitempty, so the CLI dropped
{"content":""} before sending and clearing a translation silently no-op'd.
Marking them nullable generates *NullableString, which preserves an empty
string on the wire.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

API changelog (oasdiff)

Doc-only edits (descriptions, examples) do not appear here.

10 changes: 0 error, 0 warning, 10 info
info	[response-optional-property-added] at doc/compiled.json
	in API GET /accounts/{account_id}/automations
		added the optional property `items/include_branches` to the response with the `200` status

info	[new-optional-request-property] at doc/compiled.json
	in API POST /accounts/{account_id}/automations
		added the new optional request property `include_branches`

info	[response-optional-property-added] at doc/compiled.json
	in API POST /accounts/{account_id}/automations
		added the optional property `include_branches` to the response with the `201` status

info	[response-optional-property-added] at doc/compiled.json
	in API GET /accounts/{account_id}/automations/{automation_id}
		added the optional property `include_branches` to the response with the `200` status

info	[new-optional-request-property] at doc/compiled.json
	in API PATCH /accounts/{account_id}/automations/{automation_id}
		added the new optional request property `include_branches`

info	[response-optional-property-added] at doc/compiled.json
	in API PATCH /accounts/{account_id}/automations/{automation_id}
		added the optional property `include_branches` to the response with the `200` status

info	[response-optional-property-added] at doc/compiled.json
	in API POST /accounts/{account_id}/automations/{automation_id}/activate
		added the optional property `include_branches` to the response with the `200` status

info	[response-optional-property-added] at doc/compiled.json
	in API POST /accounts/{account_id}/automations/{automation_id}/deactivate
		added the optional property `include_branches` to the response with the `200` status

info	[request-property-became-nullable] at doc/compiled.json
	in API PATCH /projects/{project_id}/keys/{id}
		the request property `description` became nullable

info	[request-property-became-nullable] at doc/compiled.json
	in API PATCH /projects/{project_id}/translations/{id}
		the request property `content` became nullable

…n #STRINGS-3652

The CLI and Go client still drop {"content":null}; only document the
empty string as the way to clear content.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Same omitempty issue as translation update: an empty description was
dropped client-side, so clearing a key description silently no-op'd.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@bikmazefe Efe Bikmaz (bikmazefe) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two review notes on the nullable change.

description: Translation content
description: Translation content. Send an empty string to clear the translation's content; omit the field to leave it unchanged.
type: string
nullable: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullable: true advertises null as a valid value, but the description tells callers to send an empty string, and the PR notes {"content":null} is still dropped by the CLI. The spec, the prose and the CLI behaviour disagree: generated clients will accept null, while the CLI silently ignores it. Consider either documenting the null semantics (and the CLI limitation) or rewording the description so it doesn't imply null is unsupported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fair point. Checking the API code showed null clears content and description, so the descriptions now say an empty string or null clears the value, and that the CLI drops null (use "" there). I also removed nullable from branch and plural_suffix: branch is only a selector (null is ignored) and plural_suffix is NOT NULL in the DB. Pushed in 4a610f9. Note the null behavior comes from reading the API code, not from running requests.

Comment thread paths/translations/update.yaml Outdated
branch:
description: specify the branch to use
type: string
nullable: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the generated Go model type for branch, content and plural_suffix (and description in keys update) from string to *NullableString. That is source-breaking for typed phrase-go consumers, e.g. body.Content = "x" stops compiling, and other typed clients (Java, Swift, TypeScript) change field types too. oasdiff only reports it as info. Could the PR description call this out and say what release/version bump it needs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: I generated the Go client from main and from this branch. body.Content = "x" no longer compiles (string -> *NullableString); the scope is now only content and description after dropping branch and plural_suffix. The PR body has a "Client impact" section with the migration snippet and the release-note question. Version bump (fix(API) vs fix(API)!:) is still open for the release owners; other typed clients were not inspected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…STRINGS-3652

branch is ignored when null and plural_suffix is NOT NULL in the DB, so
neither should be advertised as nullable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Varpuspaavi Mikko Karkee (Varpuspaavi) changed the title fix(API): make translation update content/branch/plural_suffix nullable #STRINGS-3652 fix(API): make translation content and key description nullable #STRINGS-3652 Oct 19, 2026
@Varpuspaavi

Copy link
Copy Markdown
Contributor Author

Update after review, Efe Bikmaz (@bikmazefe):

  • Narrowed scope to translation content and key description. branch (null is ignored, it is only a selector) and plural_suffix (NOT NULL column, null can 422/500) are no longer nullable. Title and body updated; pushed in 4a610f9.
  • Descriptions now say an empty string or null clears the value, and that the CLI drops null (use "" there). The null behavior comes from reading the API code, not from running requests.
  • Client impact is documented in the PR body, with a Go migration snippet. body.Content = "x" no longer compiles (string -> *NullableString), checked by generating the Go client from main and from this branch.
  • phrase-cli still builds, vets and passes its tests against the branch-generated phrase-go, because it unmarshals --data into the struct and does not assign these fields directly. This used a locally generated client, not a published release.

Open: the version bump. Title is fix(API) (patch), but typed Go consumers get a compile error. Could the release owner confirm a patch bump with a changelog note is fine, or whether this should be fix(API)!:? Java, Swift and TypeScript field types were not inspected.

Re-review would be appreciated.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants