Skip to content

Translated gift subscription Open Graph preview#27675

Merged
mike182uk merged 1 commit intomainfrom
gift-subs-preview-i18n
May 6, 2026
Merged

Translated gift subscription Open Graph preview#27675
mike182uk merged 1 commit intomainfrom
gift-subs-preview-i18n

Conversation

@mike182uk
Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/BER-3529

  • the social media preview rendered at /gift/:token (title, description, noscript fallback) was hardcoded English, leaving non-English sites with mismatched OG metadata when gift links are shared
  • wrapped user-facing strings with t() and added 5 new keys to the ghost namespace
  • duration label uses the established 1 X / {n} Xs two-key split (matching portal.json) rather than appending s to a bare unit, since plural rules and word order vary by locale
  • initialised i18n in the controller test before requiring the controller so the destructured t import resolves to the live i18next instance

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Walkthrough

getCadenceLabel in the gift-preview controller was changed to return localized cadence text via the i18n t helper (special-casing yearly, defaulting to monthly). Open Graph metadata generation (ogTitle, ogDescription) now uses t(...) with ogTitle interpolating cadenceLabel and siteTitle (interpolation escaping disabled). The <noscript> fallback link text was replaced with t('Redeem your gift subscription'). Tests initialize the i18n service before requiring the controller. New pluralization and gift-redemption keys were added across many locale JSON files.

Possibly related PRs

  • TryGhost/Ghost#27635: Modifies gifting copy and localization for gift subscriptions, adding and using overlapping gift/cadence i18n keys.

Suggested reviewers

  • sagzy
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: translating the gift subscription Open Graph preview to support multiple languages.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the problem (hardcoded English), the solution (i18n wrapping), implementation details, and test initialization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gift-subs-preview-i18n

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
ghost/core/test/unit/server/web/gift-preview/controller.test.js (1)

9-12: ⚡ Quick win

Add one non-English regression case.

This setup fix makes the controller pick up the initialized translator, but the suite still only asserts English fallback strings. Since this PR is specifically about non-English OG output, please add one test that switches to a non-English locale and checks the translated og:title, og:description, or noscript CTA so this regression stays covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/server/web/gift-preview/controller.test.js` around lines
9 - 12, Add a regression test in the existing gift-preview controller unit suite
that switches the i18n locale to a non-English language (e.g., call the i18n
instance's locale/setLocale to 'es' or 'de' before requiring or invoking the
controller) and asserts that the rendered output contains the translated
string(s) instead of the English fallback; target the same outputs already
checked (for example check the controller's rendered og:title, og:description or
the noscript CTA) so the new test verifies the controller uses the initialized
translator (refer to the existing i18n.init() setup and the controller under
test in this file).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/web/gift-preview/controller.js`:
- Around line 6-11: The getCadenceLabel function currently uses a manual
singular/plural ternary which breaks languages with multiple plural forms;
update both branches to use i18next count-based pluralization (e.g., call t with
keys like '{count} year' and '{count} month' and pass {count: duration}) so
i18next can pick the correct plural form for the locale; update the two places
where t is invoked in getCadenceLabel (the 'year' branch and the default/month
branch) to use the '{count} ...' key and count: duration argument.

---

Nitpick comments:
In `@ghost/core/test/unit/server/web/gift-preview/controller.test.js`:
- Around line 9-12: Add a regression test in the existing gift-preview
controller unit suite that switches the i18n locale to a non-English language
(e.g., call the i18n instance's locale/setLocale to 'es' or 'de' before
requiring or invoking the controller) and asserts that the rendered output
contains the translated string(s) instead of the English fallback; target the
same outputs already checked (for example check the controller's rendered
og:title, og:description or the noscript CTA) so the new test verifies the
controller uses the initialized translator (refer to the existing i18n.init()
setup and the controller under test in this file).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7bf31bbd-1041-4992-b9f2-4712bb98daf8

📥 Commits

Reviewing files that changed from the base of the PR and between 06bf5a7 and c1cfe04.

📒 Files selected for processing (65)
  • ghost/core/core/server/web/gift-preview/controller.js
  • ghost/core/test/unit/server/web/gift-preview/controller.test.js
  • ghost/i18n/locales/af/ghost.json
  • ghost/i18n/locales/ar/ghost.json
  • ghost/i18n/locales/bg/ghost.json
  • ghost/i18n/locales/bn/ghost.json
  • ghost/i18n/locales/bs/ghost.json
  • ghost/i18n/locales/ca/ghost.json
  • ghost/i18n/locales/context.json
  • ghost/i18n/locales/cs/ghost.json
  • ghost/i18n/locales/da/ghost.json
  • ghost/i18n/locales/de-CH/ghost.json
  • ghost/i18n/locales/de/ghost.json
  • ghost/i18n/locales/el/ghost.json
  • ghost/i18n/locales/en/ghost.json
  • ghost/i18n/locales/eo/ghost.json
  • ghost/i18n/locales/es/ghost.json
  • ghost/i18n/locales/et/ghost.json
  • ghost/i18n/locales/eu/ghost.json
  • ghost/i18n/locales/fa/ghost.json
  • ghost/i18n/locales/fi/ghost.json
  • ghost/i18n/locales/fr/ghost.json
  • ghost/i18n/locales/gd/ghost.json
  • ghost/i18n/locales/he/ghost.json
  • ghost/i18n/locales/hi/ghost.json
  • ghost/i18n/locales/hr/ghost.json
  • ghost/i18n/locales/hu/ghost.json
  • ghost/i18n/locales/id/ghost.json
  • ghost/i18n/locales/is/ghost.json
  • ghost/i18n/locales/it/ghost.json
  • ghost/i18n/locales/ja/ghost.json
  • ghost/i18n/locales/ko/ghost.json
  • ghost/i18n/locales/kz/ghost.json
  • ghost/i18n/locales/lt/ghost.json
  • ghost/i18n/locales/lv/ghost.json
  • ghost/i18n/locales/mk/ghost.json
  • ghost/i18n/locales/mn/ghost.json
  • ghost/i18n/locales/ms/ghost.json
  • ghost/i18n/locales/nb/ghost.json
  • ghost/i18n/locales/ne/ghost.json
  • ghost/i18n/locales/nl/ghost.json
  • ghost/i18n/locales/nn/ghost.json
  • ghost/i18n/locales/pa/ghost.json
  • ghost/i18n/locales/pl/ghost.json
  • ghost/i18n/locales/pt-BR/ghost.json
  • ghost/i18n/locales/pt/ghost.json
  • ghost/i18n/locales/ro/ghost.json
  • ghost/i18n/locales/ru/ghost.json
  • ghost/i18n/locales/si/ghost.json
  • ghost/i18n/locales/sk/ghost.json
  • ghost/i18n/locales/sl/ghost.json
  • ghost/i18n/locales/sq/ghost.json
  • ghost/i18n/locales/sr-Cyrl/ghost.json
  • ghost/i18n/locales/sr/ghost.json
  • ghost/i18n/locales/sv/ghost.json
  • ghost/i18n/locales/sw/ghost.json
  • ghost/i18n/locales/ta/ghost.json
  • ghost/i18n/locales/th/ghost.json
  • ghost/i18n/locales/tr/ghost.json
  • ghost/i18n/locales/uk/ghost.json
  • ghost/i18n/locales/ur/ghost.json
  • ghost/i18n/locales/uz/ghost.json
  • ghost/i18n/locales/vi/ghost.json
  • ghost/i18n/locales/zh-Hant/ghost.json
  • ghost/i18n/locales/zh/ghost.json

Comment thread ghost/core/core/server/web/gift-preview/controller.js Outdated
ref https://linear.app/ghost/issue/BER-3529

- the social media preview rendered at `/gift/:token` (title, description, noscript fallback) was hardcoded English, leaving non-English sites with mismatched OG metadata when gift links are shared
- wrapped user-facing strings with `t()` and added 5 new keys to the ghost namespace
- duration label uses the established `1 X` / `{n} Xs` two-key split (matching `portal.json`) rather than appending `s` to a bare unit, since plural rules and word order vary by locale
- initialised i18n in the controller test before requiring the controller so the destructured `t` import resolves to the live `i18next` instance
@mike182uk mike182uk force-pushed the gift-subs-preview-i18n branch from c1cfe04 to 465fe00 Compare May 5, 2026 17:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/i18n/locales/context.json`:
- Around line 427-430: Add context entries for the Slovak-specific plural keys
({count} month_few, {count} month_many, {count} year_few, {count} year_many)
into the context.json alongside the existing {count} month_one/{count}
month_other/{count} year_one/{count} year_other entries; use the same gift
subscription Open Graph preview descriptions as the corresponding month/year
entries (explaining singular/plural usage, example text like '1 month'/'3
months' or '1 year'/'2 years', and that translators may add language-specific
plural forms) so translators have full guidance for the Slovak plural variants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7077a431-61a3-438a-84ab-49317cb7871d

📥 Commits

Reviewing files that changed from the base of the PR and between c1cfe04 and 465fe00.

📒 Files selected for processing (65)
  • ghost/core/core/server/web/gift-preview/controller.js
  • ghost/core/test/unit/server/web/gift-preview/controller.test.js
  • ghost/i18n/locales/af/ghost.json
  • ghost/i18n/locales/ar/ghost.json
  • ghost/i18n/locales/bg/ghost.json
  • ghost/i18n/locales/bn/ghost.json
  • ghost/i18n/locales/bs/ghost.json
  • ghost/i18n/locales/ca/ghost.json
  • ghost/i18n/locales/context.json
  • ghost/i18n/locales/cs/ghost.json
  • ghost/i18n/locales/da/ghost.json
  • ghost/i18n/locales/de-CH/ghost.json
  • ghost/i18n/locales/de/ghost.json
  • ghost/i18n/locales/el/ghost.json
  • ghost/i18n/locales/en/ghost.json
  • ghost/i18n/locales/eo/ghost.json
  • ghost/i18n/locales/es/ghost.json
  • ghost/i18n/locales/et/ghost.json
  • ghost/i18n/locales/eu/ghost.json
  • ghost/i18n/locales/fa/ghost.json
  • ghost/i18n/locales/fi/ghost.json
  • ghost/i18n/locales/fr/ghost.json
  • ghost/i18n/locales/gd/ghost.json
  • ghost/i18n/locales/he/ghost.json
  • ghost/i18n/locales/hi/ghost.json
  • ghost/i18n/locales/hr/ghost.json
  • ghost/i18n/locales/hu/ghost.json
  • ghost/i18n/locales/id/ghost.json
  • ghost/i18n/locales/is/ghost.json
  • ghost/i18n/locales/it/ghost.json
  • ghost/i18n/locales/ja/ghost.json
  • ghost/i18n/locales/ko/ghost.json
  • ghost/i18n/locales/kz/ghost.json
  • ghost/i18n/locales/lt/ghost.json
  • ghost/i18n/locales/lv/ghost.json
  • ghost/i18n/locales/mk/ghost.json
  • ghost/i18n/locales/mn/ghost.json
  • ghost/i18n/locales/ms/ghost.json
  • ghost/i18n/locales/nb/ghost.json
  • ghost/i18n/locales/ne/ghost.json
  • ghost/i18n/locales/nl/ghost.json
  • ghost/i18n/locales/nn/ghost.json
  • ghost/i18n/locales/pa/ghost.json
  • ghost/i18n/locales/pl/ghost.json
  • ghost/i18n/locales/pt-BR/ghost.json
  • ghost/i18n/locales/pt/ghost.json
  • ghost/i18n/locales/ro/ghost.json
  • ghost/i18n/locales/ru/ghost.json
  • ghost/i18n/locales/si/ghost.json
  • ghost/i18n/locales/sk/ghost.json
  • ghost/i18n/locales/sl/ghost.json
  • ghost/i18n/locales/sq/ghost.json
  • ghost/i18n/locales/sr-Cyrl/ghost.json
  • ghost/i18n/locales/sr/ghost.json
  • ghost/i18n/locales/sv/ghost.json
  • ghost/i18n/locales/sw/ghost.json
  • ghost/i18n/locales/ta/ghost.json
  • ghost/i18n/locales/th/ghost.json
  • ghost/i18n/locales/tr/ghost.json
  • ghost/i18n/locales/uk/ghost.json
  • ghost/i18n/locales/ur/ghost.json
  • ghost/i18n/locales/uz/ghost.json
  • ghost/i18n/locales/vi/ghost.json
  • ghost/i18n/locales/zh-Hant/ghost.json
  • ghost/i18n/locales/zh/ghost.json
✅ Files skipped from review due to trivial changes (24)
  • ghost/core/test/unit/server/web/gift-preview/controller.test.js
  • ghost/i18n/locales/is/ghost.json
  • ghost/i18n/locales/ko/ghost.json
  • ghost/i18n/locales/bs/ghost.json
  • ghost/i18n/locales/sr-Cyrl/ghost.json
  • ghost/i18n/locales/mn/ghost.json
  • ghost/i18n/locales/th/ghost.json
  • ghost/i18n/locales/nn/ghost.json
  • ghost/i18n/locales/ru/ghost.json
  • ghost/i18n/locales/hi/ghost.json
  • ghost/i18n/locales/zh-Hant/ghost.json
  • ghost/i18n/locales/fi/ghost.json
  • ghost/i18n/locales/af/ghost.json
  • ghost/i18n/locales/sl/ghost.json
  • ghost/i18n/locales/uk/ghost.json
  • ghost/i18n/locales/nb/ghost.json
  • ghost/i18n/locales/he/ghost.json
  • ghost/i18n/locales/fa/ghost.json
  • ghost/i18n/locales/sr/ghost.json
  • ghost/i18n/locales/si/ghost.json
  • ghost/i18n/locales/pa/ghost.json
  • ghost/i18n/locales/ca/ghost.json
  • ghost/i18n/locales/cs/ghost.json
  • ghost/i18n/locales/sq/ghost.json
🚧 Files skipped from review as they are similar to previous changes (13)
  • ghost/i18n/locales/el/ghost.json
  • ghost/i18n/locales/fr/ghost.json
  • ghost/i18n/locales/hu/ghost.json
  • ghost/i18n/locales/pt-BR/ghost.json
  • ghost/i18n/locales/gd/ghost.json
  • ghost/i18n/locales/it/ghost.json
  • ghost/i18n/locales/ro/ghost.json
  • ghost/i18n/locales/eu/ghost.json
  • ghost/i18n/locales/uz/ghost.json
  • ghost/i18n/locales/ne/ghost.json
  • ghost/i18n/locales/vi/ghost.json
  • ghost/i18n/locales/pt/ghost.json
  • ghost/i18n/locales/bn/ghost.json

Comment thread ghost/i18n/locales/context.json
"{count} year_two": "",
"{count} year_few": "",
"{count} year_many": "",
"{count} year_other": "",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure we need these? 🤔 Wouldn't it be sufficient to have:

"{count} month": "",
"{count} months": "",
"{count} year": "",
"{count} years": "",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Its what our i18n tool generates - this might explain it better

}

return t('{count} month', {count: duration});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: works today, but would need adapting if we introduce 3 months, 6 months, etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Update: actually I'm not sure anymore by looking at the generated translations lol. Does it cover plural forms already?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, thats what the year_many is for

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah ok! TIL. All good then!

Comment thread ghost/i18n/locales/bg/ghost.json
@mike182uk mike182uk merged commit 2127001 into main May 6, 2026
43 checks passed
@mike182uk mike182uk deleted the gift-subs-preview-i18n branch May 6, 2026 10:27
lujuldotcom pushed a commit to lujuldotcom/Ghost that referenced this pull request May 6, 2026
ref https://linear.app/ghost/issue/BER-3529

- the social media preview rendered at `/gift/:token` (title,
description, noscript fallback) was hardcoded English, leaving
non-English sites with mismatched OG metadata when gift links are shared
- wrapped user-facing strings with `t()` and added 5 new keys to the
ghost namespace
- duration label uses the established `1 X` / `{n} Xs` two-key split
(matching `portal.json`) rather than appending `s` to a bare unit, since
plural rules and word order vary by locale
- initialised i18n in the controller test before requiring the
controller so the destructured `t` import resolves to the live `i18next`
instance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants