Skip to content

[codex] Add Cloudflare AI landing translation layer#550

Open
riderx wants to merge 10 commits intomainfrom
codex/dynamic-landing-ai-translation
Open

[codex] Add Cloudflare AI landing translation layer#550
riderx wants to merge 10 commits intomainfrom
codex/dynamic-landing-ai-translation

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 1, 2026

What changed

  • Replaced the static landing-page locale maintenance path with a Cloudflare Worker translation layer for the top 30 locales.
  • Added Workers AI HTML translation, per-page cache versioning, locale-aware canonical/hreflang output, sitemap alternates, and a dynamic language selector.

Why

  • Keep English as the source of truth in the repo while serving indexable translated landing URLs dynamically from Cloudflare cache.

Validation

  • Targeted TypeScript check for the new translation files passed.
  • Full build is currently blocked by existing repo issues unrelated to this branch.

Known blockers outside this PR

  • astro-heroicons import failures in src/config/plugins.ts.
  • Existing Starlight dependency typing mismatches.
  • Cloudflare adapter build path needs an explicit account_id for non-interactive local builds.

Summary by CodeRabbit

  • New Features

    • On‑demand AI translations for landing pages with caching, translation metadata, and fallback behavior
    • Locale-aware sitemap entries with alternate‑language links for better SEO
  • Improvements

    • Footer language selector shows native names, flags, wider dropdown and improved accessibility
    • Updated Open Graph/Twitter URL and locale handling for more accurate social previews
    • Site now consistently derives display locale and supports RTL rendering
  • Chores

    • Added an HTML parsing/runtime translation dependency

@socket-security
Copy link
Copy Markdown

socket-security bot commented Apr 1, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedlinkedom@​0.18.129410010082100

View full report

@socket-security
Copy link
Copy Markdown

socket-security bot commented Apr 1, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm linkedom is 92.0% likely obfuscated

Confidence: 0.92

Location: Package overview

From: package.jsonnpm/linkedom@0.18.12

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/linkedom@0.18.12. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 614e928bf3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes Paraglide/i18n integrations; adds landing-locale service, runtime Cloudflare Worker HTML translation (with AI + linkedom), cache-backed middleware, generated pageVersions, sitemap URL/lastmod normalization, and replaces many message imports with a new messages service.

Changes

Cohort / File(s) Summary
Build config & sitemap
astro.config.mjs, wrangler.jsonc
Removed Paraglide/astro-i18n integrations; generate src/generated/pageVersions.ts at config load; normalize sitemap paths, resolve lastmod via locale-stripped fallback, and emit alternate-locale links; added ai binding in wrangler.jsonc.
Dependencies
package.json
Removed Paraglide/astro-i18n packages; added linkedom.
Generated artifact
src/generated/pageVersions.ts
New generated export: siteBuildVersion and pageVersionMap (ISO timestamps).
Type declarations
src/env.d.ts
Added cloudflare:workers ambient module; replaced required translations local with optional locals: displayLocale, requestedLocale, requestedPathname, requestedUrl, isDynamicLandingRequest.
Landing-locale service
src/services/landingLocale.ts
New centralized landing-locale definitions and helpers: locale metadata, RTL detection, pathname normalization/parsing, dynamic-path detection, localized pathname builders, getAlternateLocaleEntries, getOgLocale, and sitemap hreflang maps.
Messages service
src/services/messages.ts
New default-export messages shim that loads locale JSON catalogs and exposes message functions via a Proxy with placeholder formatting and per-key missing-key warnings.
Translation engine
src/lib/landingTranslation.ts
New exported translateLandingHtml(...): parses HTML with linkedom, extracts translatable text/attrs/JSON-LD (with masking), batches and AI-translates, validates JSON response, unmasks and applies translations, returns serialized HTML.
Middleware runtime
src/middleware.ts
Reworked middleware: initialize runtime locals, detect/route dynamic landing requests, header bypass support, build cache keys with siteBuildVersion & pageVersion, Cloudflare cache lookup/write, fetch origin source, call translateLandingHtml, set translation/version headers, conditional caching/ETag, and fallback behavior on errors.
Alternate links & utils
src/lib/alternateVersions.ts
Now reads pathname from Astro.locals.requestedPathname when available and delegates alternate-locale construction to getAlternateLocaleEntries() instead of manual prefix manipulation.
Components & pages (mass import changes)
many src/components/*, src/pages/*, src/layouts/Layout.astro, src/components/SEO.astro, src/components/Footer.astro
Switched from @/paraglide/messages namespace imports to import m from '@/services/messages'; many components/pages now use Astro.locals.displayLocale and landingLocale helpers (getLocaleEntry, getAlternateLocaleEntries, getOgLocale, isRtlLocale) for locale, URL, and UI rendering (language selector, ARIA, canonicalization).
Blog page
src/pages/blog/[slug].astro
Removed per-page computation/assignment of alternateVersions from blog content.
Docs & guidance
README.md, CLAUDE.md
Documentation updated to describe runtime/demand-driven translation and renamed message source to src/services/messages.
SEO checker config
seo-checker.config.json
Adjusted distPath and reduced languages list for SEO checks.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Middleware
    participant Cache
    participant Origin as OriginRenderer
    participant AI as CloudflareAI

    Client->>Middleware: GET /{locale?}/{path}
    Middleware->>Middleware: parseRequestedLandingLocale(), set locals
    Middleware->>Cache: GET cacheKey(siteBuildVersion,pageVersion,locale,path)
    alt cache hit
        Cache-->>Middleware: cached translated Response
        Middleware-->>Client: return cached Response (translation headers)
    else cache miss
        Middleware->>Origin: fetch source page (with x-capgo-translation-source)
        Origin-->>Middleware: source HTML (default locale)
        Middleware->>AI: translateLandingHtml(source HTML, locale, siteOrigin)
        AI-->>AI: parse, mask, call model, unmask, apply translations
        AI-->>Middleware: translated HTML
        Middleware->>Cache: PUT cacheKey -> translated Response (async)
        Middleware-->>Client: return translated Response (translation headers)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 I hopped through paths both near and far,
Masked the links and taught AI to spar,
Cached each carrot, stamped each page with time,
Flags waved, locales hummed their native rhyme,
A hopping site — translated on a dime! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a Cloudflare AI-powered translation layer for landing pages. It is specific, descriptive, and accurately reflects the primary objective of the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/dynamic-landing-ai-translation

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 057edd92a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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: 5

🧹 Nitpick comments (4)
src/lib/alternateVersions.ts (1)

1-1: Unused import: defaultLocale is imported but never used.

The defaultLocale import from @/services/locale is not referenced in this file after the refactor.

♻️ Remove unused import
-import { defaultLocale } from '@/services/locale'
 import { getAlternateLocaleEntries } from '@/services/landingLocale'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/alternateVersions.ts` at line 1, Remove the unused import
defaultLocale from the top of the file; locate the import statement that reads
"import { defaultLocale } from '@/services/locale'" in alternateVersions.ts and
delete defaultLocale (or the entire import line if nothing else is imported) so
there are no unused symbols left.
src/services/landingLocale.ts (1)

56-69: Minor redundancy: /docs/ appears in both exact and prefix excludes.

Line 57 includes /docs/ in DYNAMIC_LANDING_EXACT_EXCLUDES, but line 69 also has /docs/ in DYNAMIC_LANDING_PREFIX_EXCLUDES. The prefix check already covers the exact match, making the exact entry redundant.

♻️ Suggested cleanup
 const DYNAMIC_LANDING_EXACT_EXCLUDES = new Set<string>([
-  '/docs/',
-  '/blog/',
   '/robots.txt',
   '/favicon.ico',
   '/favicon.svg',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/landingLocale.ts` around lines 56 - 69,
DYNAMIC_LANDING_EXACT_EXCLUDES contains a redundant '/docs/' entry because
DYNAMIC_LANDING_PREFIX_EXCLUDES already handles that path prefix; remove the
'/docs/' string from the DYNAMIC_LANDING_EXACT_EXCLUDES Set so only the prefix
array (DYNAMIC_LANDING_PREFIX_EXCLUDES) covers '/docs/' and avoid duplicate
exclusions.
astro.config.mjs (2)

67-67: File generation runs at config evaluation, not build time.

writeGeneratedPageVersionModule is called at module scope during config evaluation. This means:

  1. Every astro dev restart regenerates with a fresh siteBuildVersion timestamp
  2. The file change could trigger file watchers, potentially causing rebuild loops
  3. Timestamp drifts from actual build completion time

Consider using an Astro integration hook like astro:build:start to generate this file only during builds, or gate the call behind a build-mode check.

♻️ Proposed fix using build mode check
 const pageLastModDates = getPageLastModDates()
-writeGeneratedPageVersionModule(pageLastModDates)
+
+// Only regenerate during builds to avoid dev-mode file watcher loops
+if (process.env.NODE_ENV === 'production' || process.argv.includes('build')) {
+  writeGeneratedPageVersionModule(pageLastModDates)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@astro.config.mjs` at line 67, The call to writeGeneratedPageVersionModule is
executed at module scope during config evaluation causing dev restarts and
watcher issues; modify astro.config.mjs to only invoke
writeGeneratedPageVersionModule during actual builds by either registering it
inside an Astro integration hook (e.g., listen for "astro:build:start") or by
checking build mode before calling it (use Astro's mode or process.env to detect
build vs dev), ensuring you reference the existing
writeGeneratedPageVersionModule and siteBuildVersion symbols and move the
invocation out of top-level module scope into the build-start hook or a gated
conditional.

74-76: Normalize path keys when generating the map for consistency with middleware lookups.

The middleware normalizes pathnames before looking up in pageVersionMap (src/middleware.ts:178). While the current implementation works due to blog posts adding both variants (with and without trailing slash), explicitly normalizing keys when writing them makes the consistency explicit and prevents issues if new paths are added in the future.

The normalizePathname function is already imported; consider applying it when generating the map:

♻️ Proposed normalization
  const entries = [...lastModMap.entries()]
    .sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath))
-   .map(([path, lastMod]) => `  ${JSON.stringify(path)}: ${JSON.stringify(lastMod.toISOString())},`)
+   .map(([path, lastMod]) => `  ${JSON.stringify(normalizePathname(path))}: ${JSON.stringify(lastMod.toISOString())},`)
    .join('\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@astro.config.mjs` around lines 74 - 76, The pageVersionMap keys should be
normalized to match the middleware lookup; update the map-building code in
astro.config.mjs to call normalizePathname on each path key before assigning it
to pageVersionMap so the keys align with the middleware's normalizePathname
usage (ensure you use the existing normalizePathname import and update the code
that populates pageVersionMap accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Footer.astro`:
- Around line 309-316: The language toggle button
(id="language-dropdown-button") currently removes the focus outline and does not
expose state to assistive tech; add aria-controls="language-dropdown" and an
aria-expanded attribute on that button and implement a click handler for the
toggle that flips the dropdown (id="language-dropdown") visibility and updates
button.setAttribute('aria-expanded', String(isOpen)); ensure the document click
handler hides the dropdown and sets aria-expanded="false" when clicking outside;
restore a visible keyboard focus style (use a focus-visible class or CSS rule
instead of removing focus outline) so the button remains keyboard accessible.

In `@src/components/SEO.astro`:
- Around line 51-53: The SEO component currently copies requestUrl.toString()
(via urlString and canonicalString) which preserves query parameters; change the
logic in src/components/SEO.astro so that you derive canonical/social URLs from
the request URL with query/search removed (e.g., use requestUrl.origin +
requestUrl.pathname or set search = '' on a URL object) and assign that cleaned
value to canonicalString and any social URL variables instead of the full
requestUrl.toString(); ensure any dynamic branch that builds urlString does the
same to avoid preserving utm_/preview/cache-buster query params.

In `@src/lib/landingTranslation.ts`:
- Around line 293-317: The chunkMaskedValues function currently pushes an empty
chunk when the first entry itself exceeds the 5000-char limit; update
chunkMaskedValues so it never pushes an empty chunk and enforces the
5000-character ceiling per output chunk: before you push currentChunk when
(currentChunk.length >= 24 || nextLength > 5000) check if currentChunk is empty
— if it is and entry.maskedValue.length > 5000, split that single
entry.maskedValue into multiple smaller entries (preserving the original entry's
metadata) and append those pieces as their own chunks, otherwise only push
currentChunk when it contains items; use the function name chunkMaskedValues and
variables currentChunk/currentLength/entry to locate where to add the guard and
splitting logic.
- Around line 203-224: The function rewriteJsonLdValue currently runs every
string through localizeInternalUrl which causes plain text (e.g.,
name/description) to be treated as URLs; change the string branch in
rewriteJsonLdValue to only call localizeInternalUrl when the string looks like a
URL (e.g., matches a URL-like pattern such as starting with "http://",
"https://", or "/") and otherwise return the original string; update the check
in rewriteJsonLdValue (before calling localizeInternalUrl) to use a simple
regexp or URL-like test and keep existing behavior for arrays/objects and the
inLanguage special-case.

In `@src/middleware.ts`:
- Around line 85-108: The middleware currently sets context.locals.displayLocale
(and related requested* fields) to the target locale before calling
next(sourceRequest), which causes untranslated source HTML to be returned
advertising the requested locale; change the flow in paraglideMiddleware so that
context.locals.displayLocale remains the defaultLocale (and
requestedLocale/requestedPathname/requestedUrl still stored separately) while
calling next(sourceRequest), and only assign context.locals.displayLocale =
requestedLocale after you confirm translation will be applied (e.g., after AI
existence and successful translation path); alternatively, if you must return
the original sourceResponse (in the disabled/error branches of the AI check or
when isHtmlResponse check fails), ensure you reset context.locals.displayLocale
back to defaultLocale (and do not set lang/canonical/hreflang metadata for
requestedLocale) before returning via withTranslationHeaders so untranslated
HTML never advertises the requested locale.

---

Nitpick comments:
In `@astro.config.mjs`:
- Line 67: The call to writeGeneratedPageVersionModule is executed at module
scope during config evaluation causing dev restarts and watcher issues; modify
astro.config.mjs to only invoke writeGeneratedPageVersionModule during actual
builds by either registering it inside an Astro integration hook (e.g., listen
for "astro:build:start") or by checking build mode before calling it (use
Astro's mode or process.env to detect build vs dev), ensuring you reference the
existing writeGeneratedPageVersionModule and siteBuildVersion symbols and move
the invocation out of top-level module scope into the build-start hook or a
gated conditional.
- Around line 74-76: The pageVersionMap keys should be normalized to match the
middleware lookup; update the map-building code in astro.config.mjs to call
normalizePathname on each path key before assigning it to pageVersionMap so the
keys align with the middleware's normalizePathname usage (ensure you use the
existing normalizePathname import and update the code that populates
pageVersionMap accordingly).

In `@src/lib/alternateVersions.ts`:
- Line 1: Remove the unused import defaultLocale from the top of the file;
locate the import statement that reads "import { defaultLocale } from
'@/services/locale'" in alternateVersions.ts and delete defaultLocale (or the
entire import line if nothing else is imported) so there are no unused symbols
left.

In `@src/services/landingLocale.ts`:
- Around line 56-69: DYNAMIC_LANDING_EXACT_EXCLUDES contains a redundant
'/docs/' entry because DYNAMIC_LANDING_PREFIX_EXCLUDES already handles that path
prefix; remove the '/docs/' string from the DYNAMIC_LANDING_EXACT_EXCLUDES Set
so only the prefix array (DYNAMIC_LANDING_PREFIX_EXCLUDES) covers '/docs/' and
avoid duplicate exclusions.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ad3c7b9-0f60-458c-af88-edb410a33209

📥 Commits

Reviewing files that changed from the base of the PR and between e85cc47 and 614e928.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • src/generated/pageVersions.ts is excluded by !**/generated/**
📒 Files selected for processing (11)
  • astro.config.mjs
  • package.json
  • src/components/Footer.astro
  • src/components/SEO.astro
  • src/env.d.ts
  • src/layouts/Layout.astro
  • src/lib/alternateVersions.ts
  • src/lib/landingTranslation.ts
  • src/middleware.ts
  • src/services/landingLocale.ts
  • wrangler.jsonc

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)
astro.config.mjs (1)

83-102: Page version module generation looks good; fix double-slash path.

The function correctly generates a deterministic TypeScript module with sorted entries and proper ISO date serialization. However, line 84 has the same double-slash issue as line 28.

🔧 Suggested fix for line 84
 function writeGeneratedPageVersionModule(lastModMap) {
-  mkdirSync(`${SRC_DIR}/generated`, { recursive: true })
+  mkdirSync(`${SRC_DIR}generated`, { recursive: true })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@astro.config.mjs` around lines 83 - 102, The mkdirSync call in
writeGeneratedPageVersionModule can produce a double-slash path when SRC_DIR
ends with a slash; update the directory construction to use a normalized path
(e.g., path.join or equivalent) instead of string concatenation so
mkdirSync(`${SRC_DIR}/generated`, ...) becomes mkdirSync(joined path) and
likewise ensure GENERATED_PAGE_VERSIONS_FILE is built from the normalized
SRC_DIR when writing the file; reference the function name
writeGeneratedPageVersionModule and the symbols SRC_DIR,
GENERATED_PAGE_VERSIONS_FILE, mkdirSync and writeFileSync to locate and fix the
code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@astro.config.mjs`:
- Line 28: The generated path uses a redundant slash because SRC_DIR already
ends with '/', so update the constant GENERATED_PAGE_VERSIONS_FILE to avoid
double slashes (e.g., concatenate without an extra '/' or use a join utility).
Locate GENERATED_PAGE_VERSIONS_FILE and replace
`${SRC_DIR}/generated/pageVersions.ts` with a form that does not insert an extra
slash (for example `${SRC_DIR}generated/pageVersions.ts` or path.join/SRC_DIR +
'generated/...') so the resulting path is '/src/generated/pageVersions.ts'
instead of '/src//generated/...'.

---

Nitpick comments:
In `@astro.config.mjs`:
- Around line 83-102: The mkdirSync call in writeGeneratedPageVersionModule can
produce a double-slash path when SRC_DIR ends with a slash; update the directory
construction to use a normalized path (e.g., path.join or equivalent) instead of
string concatenation so mkdirSync(`${SRC_DIR}/generated`, ...) becomes
mkdirSync(joined path) and likewise ensure GENERATED_PAGE_VERSIONS_FILE is built
from the normalized SRC_DIR when writing the file; reference the function name
writeGeneratedPageVersionModule and the symbols SRC_DIR,
GENERATED_PAGE_VERSIONS_FILE, mkdirSync and writeFileSync to locate and fix the
code.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ca4c6fe-914a-4d5d-94e9-f0623af8cb9a

📥 Commits

Reviewing files that changed from the base of the PR and between 614e928 and 057edd9.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • src/generated/pageVersions.ts is excluded by !**/generated/**
📒 Files selected for processing (3)
  • astro.config.mjs
  • package.json
  • wrangler.jsonc
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • wrangler.jsonc

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c9e549d56

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8014191e2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/pages/return.astro (1)

3-7: ⚠️ Potential issue | 🟠 Major

m.*(..., { locale }) is misleading here because locale is ignored at runtime.

After switching to @/services/messages, these calls look locale-aware but currently resolve from English templates only (per src/services/messages.ts proxy behavior). That makes title/description and page copy non-localized at render time unless an external layer always rewrites them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/return.astro` around lines 3 - 7, The current calls
m.return_policy({}, { locale: Astro.locals.locale }) and
m.read_our_return_policy({}, { locale: Astro.locals.locale }) use the proxy that
ignores the second-argument locale; update these to use the messages module's
explicit locale-aware API (either bind the locale or call the resolver that
takes locale as the primary argument) so the strings are resolved for
Astro.locals.locale at render time — e.g., obtain a locale-bound messages
instance or call the messages resolver with Astro.locals.locale and replace
usage in the title and description assignments (references: m.return_policy,
m.read_our_return_policy, Astro.locals.locale).
src/pages/live-update.astro (1)

948-951: ⚠️ Potential issue | 🟡 Minor

Replace removed text-opacity-* utilities with slash notation.

text-opacity-40 and text-opacity-20 were removed in Tailwind v4. Use the slash notation modifier instead to maintain the intended opacity on the background SVG.

♻️ Suggested change
-              <path class="text-blue-500 text-opacity-40" fill="currentColor" d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"></path>
-              <path class="text-blue-400 text-opacity-20" fill="currentColor" d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"></path>
+              <path class="text-blue-500/40" fill="currentColor" d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"></path>
+              <path class="text-blue-400/20" fill="currentColor" d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"></path>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/live-update.astro` around lines 948 - 951, SVG path elements use
removed Tailwind utilities text-opacity-40 and text-opacity-20; update the two
<path> elements (the ones with class "text-blue-500 text-opacity-40" and
"text-blue-400 text-opacity-20") to use slash opacity on the color token instead
(e.g., replace "text-blue-500 text-opacity-40" with "text-blue-500/40" and
"text-blue-400 text-opacity-20" with "text-blue-400/20") so the background SVG
keeps the intended opacity under Tailwind v4.
src/pages/top_cordova_app.astro (1)

91-101: ⚠️ Potential issue | 🟠 Major

Preserve locale on the related-page links.

These hardcoded links always route to /${l}/, causing users on localized pages to bounce back to the default-language versions when exploring other app category lists. Use the imported getRelativeLocaleUrl helper to preserve the current locale.

Suggested change
-          others.map((l) => (
-            <a href={`/${l}/`} class="flex flex-col py-8 text-center bg-gray-700 rounded-lg transition-all duration-200 hover:bg-gray-700 focus:bg-blue-900">
+          others.map((l) => (
+            <a href={getRelativeLocaleUrl(Astro.locals.locale, `/${l}/`)} class="flex flex-col py-8 text-center bg-gray-700 rounded-lg transition-all duration-200 hover:bg-gray-700 focus:bg-blue-900">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/top_cordova_app.astro` around lines 91 - 101, The anchor hrefs in
the others.map block always use `/${l}/`, losing the current locale; update the
href to call the imported `getRelativeLocaleUrl` with the target path (e.g. call
getRelativeLocaleUrl(`/${l}/`) or similar) so links preserve locale. Locate the
map that renders anchors (the arrow function mapping `others.map((l) => ...)`),
replace the hardcoded href with the `getRelativeLocaleUrl` result, and keep
other props and the `renameCat(l)` usage unchanged.
src/pages/solutions/startups.astro (1)

366-369: ⚠️ Potential issue | 🟡 Minor

Replace removed text-opacity-* utilities with v4 slash syntax.

Tailwind v4 removed text-opacity-* in favor of the slash opacity modifier. Update lines 368–369:

Suggested change
-              <path class="text-emerald-500 text-opacity-40" fill="currentColor" d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"></path>
-              <path class="text-teal-400 text-opacity-20" fill="currentColor" d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"></path>
+              <path class="text-emerald-500/40" fill="currentColor" d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"></path>
+              <path class="text-teal-400/20" fill="currentColor" d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"></path>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/solutions/startups.astro` around lines 366 - 369, Tailwind v4
removed the text-opacity-* utilities used on the two <path> elements; replace
the classes that use text-opacity-40 and text-opacity-20 with the slash-opacity
syntax on the color classes (e.g., change the class list that includes
"text-emerald-500 text-opacity-40" to use "text-emerald-500/40" and similarly
replace "text-teal-400 text-opacity-20" with "text-teal-400/20"), removing the
old text-opacity-* tokens so the <path> elements render correctly under Tailwind
v4.
src/components/doc/LanguageSelect.astro (1)

10-34: ⚠️ Potential issue | 🟠 Major

Use real, tabbable links for the locale menu.

These options are rendered as anchors without href and with tabindex="-1", so the picker is effectively mouse-only. The toggle also removes the visible focus outline and doesn't expose open state. Render actual links (or buttons with full keyboard handling), keep them tabbable, and sync aria-expanded/aria-controls.

As per coding guidelines, "All interactive elements must be keyboard accessible", "Focus states must be visible", and "Add aria-expanded to dropdown toggles".

Also applies to: 67-79

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/doc/LanguageSelect.astro` around lines 10 - 34, Replace the
non-tabbable anchor items and hidden-focus toggle with accessible controls: make
the toggle button with id="language-menu-button" retain visible focus (remove
the "focus:outline-none" utility), add aria-expanded and
aria-controls="language-menu" and toggle its boolean state when opening/closing
the menu; render each menu item from landingLocales as a real link with an href
(e.g., locale URL) and remove tabindex="-1" so they are keyboard-focusable, or
if you must use buttons implement full keyboard handling (Enter/Escape/Arrow
navigation) for the list with id="language-menu"; ensure the menu container has
appropriate role="menu" and each child role="menuitem" and preserve focus styles
so keyboard users can see focus.
♻️ Duplicate comments (5)
src/lib/landingTranslation.ts (2)

203-220: ⚠️ Potential issue | 🟠 Major

Only localize URL fields inside JSON-LD.

The string branch runs every JSON-LD string through localizeInternalUrl(). Plain-text fields like name, description, or headline then get treated as relative URLs and rewritten into bogus site paths, which corrupts the structured data.

Pass the parent key through the recursion and only rewrite known URL-valued keys.

Based on learnings: Applies to src/pages/**/*.{astro,ts,tsx,js,jsx} : Include JSON-LD structured data using src/lib/ldJson.ts helpers for SEO.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/landingTranslation.ts` around lines 203 - 220, The rewriteJsonLdValue
function is currently localizing every string (corrupting plain-text fields like
name/description); modify rewriteJsonLdValue to accept a parentKey (or key)
parameter and only call localizeInternalUrl for strings when the parent key is
one of the URL-valued JSON-LD properties (e.g., "url", "image", "sameAs", "@id",
"mainEntityOfPage", etc.), keep the existing special-case for "inLanguage" to
set locale, and propagate the current object key in recursive calls so nested
URL fields are handled correctly; update all call sites to pass an initial empty
key or undefined.

317-341: ⚠️ Potential issue | 🟠 Major

Don't emit empty or oversized translation batches.

When the first entry is already over the 5000-character limit, this pushes an empty chunk and then still sends the oversized entry as its own batch. That wastes one AI call and never enforces the cap.

🧩 Minimal guard
-    if (currentChunk.length >= 24 || nextLength > 5000) {
+    if (currentChunk.length >= 24 || (currentChunk.length > 0 && nextLength > 5000)) {
       chunks.push(currentChunk)
       currentChunk = []
       currentLength = 0
     }
 
+    if (entry.maskedValue.length > 5000) {
+      throw new Error('Translation segment exceeds the maximum chunk size.')
+    }
+
     currentChunk.push(entry)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/landingTranslation.ts` around lines 317 - 341, chunkMaskedValues
currently pushes an empty currentChunk when the first entry itself exceeds the
5000-char cap and then still emits the oversized entry; fix chunking logic in
chunkMaskedValues so you never push an empty chunk and you never emit an
oversized batch: when iterating entries compute nextLength (using entry.id and
entry.maskedValue) and if nextLength > 5000 and currentChunk is empty, create a
single-entry chunk with that entry and push it (do not push an empty
currentChunk first); otherwise if nextLength > 5000 and currentChunk is
non-empty, push currentChunk then start a new chunk for the entry; also ensure
the max-items guard (currentChunk.length >= 24) behaves similarly by only
pushing non-empty currentChunk before starting a new one and then adding the
current entry to the new chunk, and always update currentLength consistently.
src/components/SEO.astro (1)

51-53: ⚠️ Potential issue | 🟠 Major

Strip search params before emitting canonical/social URLs.

requestUrl.toString() still carries tracking/search params. On dynamic landing requests those values flow into both canonicalString and urlString, so every ?utm_* variant self-canonicalizes and gets its own social URL.

🔧 Minimal fix
-const requestedUrlString = requestUrl.toString()
+const cleanedRequestUrl = new URL(requestUrl.toString())
+cleanedRequestUrl.search = ''
+cleanedRequestUrl.hash = ''
+const requestedUrlString = cleanedRequestUrl.toString()

Based on learnings: Applies to src/pages/**/*.astro : Use canonical URLs for duplicate content.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SEO.astro` around lines 51 - 53, The current code uses
requestUrl.toString() which includes search params (utm_*), causing dynamic
landing pages to self-canonicalize with tracking querystrings; fix by
normalizing URLs to strip search params before assigning requestedUrlString,
urlString and canonicalString: create a URL object from requestUrl (and from
canonical when used), set its search to '' (and optionally hash to '' if
desired), then use its toString() (or pass that cleaned string into toStringUrl)
so that requestUrl, urlString and canonicalString are emitted without query
parameters; update the code around requestUrl/requestedUrlString,
toStringUrl(canonical) and Astro.locals.isDynamicLandingRequest to use these
cleaned URLs.
src/components/Footer.astro (1)

309-316: ⚠️ Potential issue | 🟠 Major

Expose state and visible focus on the footer language toggle.

This still removes the default outline and never updates aria-expanded, so keyboard and screen-reader users can't tell whether the dropdown is open. Add aria-controls/aria-expanded, keep the attribute in sync in both handlers, and restore a visible focus style.

As per coding guidelines, "Add aria-expanded to dropdown toggles" and "All interactive elements must be keyboard accessible with visible focus states".

Also applies to: 347-360

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Footer.astro` around lines 309 - 316, The language toggle
button (id "language-dropdown-button") currently removes visible focus and never
exposes dropdown state; add aria-controls="language-dropdown" to the button and
add an aria-expanded attribute that is kept in sync (true/false) whenever the
dropdown open/close logic runs (update the same place where you add/remove the
"hidden" class on the element with id "language-dropdown"); also restore a
visible focus style on the button (remove or replace the "focus:outline-none"
usage with a visible focus class such as a focus ring or outline via your CSS
utility classes) so keyboard users can see focus; apply the same changes to the
other language toggle instance referenced in the file (the block near the
347-360 region).
astro.config.mjs (1)

26-26: ⚠️ Potential issue | 🟡 Minor

Drop the redundant / when composing generated paths.

SRC_DIR already ends with /, so both interpolations resolve to /src//generated/....

🔧 Minimal fix
-const GENERATED_PAGE_VERSIONS_FILE = `${SRC_DIR}/generated/pageVersions.ts`
+const GENERATED_PAGE_VERSIONS_FILE = `${SRC_DIR}generated/pageVersions.ts`-  mkdirSync(`${SRC_DIR}/generated`, { recursive: true })
+  mkdirSync(`${SRC_DIR}generated`, { recursive: true })

Also applies to: 82-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@astro.config.mjs` at line 26, The generated path constants are producing a
double slash because SRC_DIR already ends with a slash; update the template for
GENERATED_PAGE_VERSIONS_FILE to remove the extra '/' between SRC_DIR and the
"generated/..." segment (i.e., concatenate SRC_DIR with "generated/..." without
a leading slash) and make the same fix for the other generated-path constant
referenced in the file (the similar interpolation around line 82) so no
generated paths contain a double slash.
🧹 Nitpick comments (5)
src/services/messages.ts (1)

24-24: Missing message keys fail open to raw key strings.

At Line 24, ?? property silently renders untranslated key names in production. That makes missing keys hard to detect and can leak internal key text into UI/metadata.

Suggested hardening
-      const template = messageTemplates[property] ?? property
-      return (params?: MessageParams) => formatMessage(template, params)
+      const template = messageTemplates[property]
+      if (!template) {
+        if (import.meta.env.DEV) {
+          console.warn(`[messages] Missing message key: ${property}`)
+        }
+        return () => property
+      }
+      return (params?: MessageParams) => formatMessage(template, params)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/messages.ts` at line 24, The current fallback `const template =
messageTemplates[property] ?? property` exposes raw keys; change it to fail-open
safely by replacing the `?? property` fallback with a clear missing-key marker
and a warning log: look up `messageTemplates[property]`, if missing call the
module/logger (e.g., `logger.warn` or `console.warn`) with the missing key and
set `template = \`[[missing:${property}]]\`` (or throw in non-production if you
prefer stricter behavior). Update usage around the `template` variable so
downstream consumers always get either a proper template or the explicit
`[[missing:...]]` sentinel instead of the raw key.
src/pages/solutions/production-updates.astro (1)

3-25: Consider adding a fail-fast guard for SEO message keys to prevent silent fallbacks.

The message keys are currently all present in the translation files. However, since @/services/messages falls back to the raw key name when an entry is missing, adding a simple guard would protect against future regressions where these critical SEO fields (title, description) or structured data keys are accidentally forgotten.

The optional refactoring below demonstrates a pattern that can be reused across all solution pages in this PR:

Optional fail-fast pattern
+const requireMessage = (key: string, value: string) => {
+  if (value === key) throw new Error(`Missing message key: ${key}`)
+  return value
+}
+
-const title = `${brand} | ${m.solutions_production_updates_title({}, { locale })}`
-const description = m.solutions_production_updates_description({}, { locale })
+const title = `${brand} | ${requireMessage(
+  'solutions_production_updates_title',
+  m.solutions_production_updates_title({}, { locale }),
+)}`
+const description = requireMessage(
+  'solutions_production_updates_description',
+  m.solutions_production_updates_description({}, { locale }),
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/solutions/production-updates.astro` around lines 3 - 25, Add a
fail-fast guard around the localized SEO strings so missing translation
fallbacks don't silently ship: after computing title and description using m
(the imported messages service) and locale, assert that those values are not
equal to the raw key or empty, and if they are, throw or log a clear error (or
call Astro.exit) so the build fails early; update the block that defines title,
description, and serviceLdJson/ldJSON to validate
m.solutions_production_updates_title and
m.solutions_production_updates_description outputs before using them in
createServiceLdJson and createLdJsonGraph.
src/pages/sponsor.astro (1)

120-120: Missing content prop on Layout component.

This page uses m.sponsor_title and m.sponsor_description for display but doesn't pass a content object to the Layout component. Per coding guidelines, pages must have unique title and description in the content object for SEO. While this appears to be a pre-existing issue (not introduced by this PR), consider addressing it.

🔍 Suggested fix

Add a content object and pass it to Layout:

 const sponsors = await fetchSponsors()
+const title = [Astro.locals.runtimeConfig.public.brand, m.sponsor_title({}, { locale: Astro.locals.locale })].join(' | ')
+const description = m.sponsor_description({}, { locale: Astro.locals.locale })
+const content = { title, description }
 const bakerSponsors = sponsors.filter((sponsor) => sponsor.tier === 'baker')
-<Layout>
+<Layout content={content}>

Based on learnings: "Page must have unique title and description in content object for SEO"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/sponsor.astro` at line 120, The Layout component usage is missing
the required content prop, causing the page to lack unique SEO
title/description; update the <Layout> invocation to pass a content object with
title: m.sponsor_title and description: m.sponsor_description (e.g., content={{
title: m.sponsor_title, description: m.sponsor_description }}), ensuring the
Layout component receives the content prop so the page has unique title and
description for SEO.
src/pages/consulting.astro (1)

263-263: Improve alt text for accessibility.

The alt text "logo" is not descriptive. As per coding guidelines, all images must have descriptive alt text that conveys the image's purpose. Since this is the Click & Boat company logo accompanying a testimonial, the alt text should reflect that.

🛠️ Suggested fix
-              <img loading="lazy" height="10" alt="logo" src="/click_and_boat.webp" class="inline-block pr-2 max-w-full align-middle border-0 w-1/8" />
+              <img loading="lazy" height="10" alt="Click & Boat logo" src="/click_and_boat.webp" class="inline-block pr-2 max-w-full align-middle border-0 w-1/8" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/consulting.astro` at line 263, Replace the non-descriptive alt
attribute on the <img> element that loads "/click_and_boat.webp" (the element
with class "inline-block pr-2 max-w-full align-middle border-0 w-1/8") with a
meaningful description — e.g., "Click & Boat company logo" or "Click & Boat logo
for testimonial" — so the image conveys its purpose to assistive technologies.
src/components/landing/MonitoringFeatures.astro (1)

98-124: Add accessibility attributes to decorative SVG charts.

Per coding guidelines, decorative SVGs should have aria-hidden="true". These chart visualizations are illustrative and don't convey critical information beyond the card headings.

♿ Suggested fix
-          <svg class="w-full h-full" viewBox="0 0 100 50" preserveAspectRatio="none">
+          <svg class="w-full h-full" viewBox="0 0 100 50" preserveAspectRatio="none" aria-hidden="true">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/landing/MonitoringFeatures.astro` around lines 98 - 124, The
SVG used for the decorative chart should be marked as
non-interactive/accessibility-hidden; update the <svg class="w-full h-full"
viewBox="0 0 100 50" preserveAspectRatio="none"> element by adding
aria-hidden="true" and focusable="false" (so assistive tech ignores it) while
leaving the paths/defs (blueGradient, greenGradient, etc.) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Footer.astro`:
- Line 316: The class string using the deprecated Tailwind utility
"ring-opacity-5" should be updated to the new slash-opacity syntax: replace
"ring-opacity-5" with "ring-black/5" inside the same class attribute (the
element with class="... absolute bottom-full left-0 z-50 mb-2 hidden w-56
rounded-md bg-[`#1c1c1f`] shadow-lg ring-1 ring-black focus:outline-none"); ensure
you preserve the surrounding classes and spacing so the element styling remains
identical.

In `@src/components/landing/DeviceLogs.astro`:
- Line 2: The component DeviceLogs.astro uses hardcoded internal links
"/register" and "/live-update" which ignore locale prefixes; import and use
getRelativeLocaleUrl from "astro:i18n" and replace those anchor hrefs to call
getRelativeLocaleUrl("/register") and getRelativeLocaleUrl("/live-update") (or
compute the localized path once and reuse) so links respect the current locale;
update the top import to include getRelativeLocaleUrl and change any usages in
DeviceLogs.astro accordingly.

In `@src/components/SEO.astro`:
- Around line 52-53: The ternaries calculating urlString and canonicalString
rely on Astro.locals.isDynamicLandingRequest which may not be set for translated
request paths; either initialize Astro.locals.isDynamicLandingRequest earlier
(e.g. before returning from handleDynamicLandingRequest in src/middleware.ts) or
change the logic in src/components/SEO.astro to derive the branch from
requestedUrl/requestedUrlString directly (use requestedUrlString presence
instead of Astro.locals.isDynamicLandingRequest) so urlString and
canonicalString always resolve correctly for translated pages.

In `@src/pages/aup.astro`:
- Line 3: Add the English-authoritative conditional notice to the AUP page
similar to tos.astro/disclaimer. In src/pages/aup.astro, insert the same
conditional block that checks Astro.locals.locale !== defaultLocale and renders
a small span with the explanatory text ("Note: This is an automatically
translated page from its English source. Only the English version should be used
for legal actions. Please refer to the English source for legal matters.") using
class="text-xs"; ensure defaultLocale is in scope (imported or referenced the
same way as in tos.astro/disclaimer) so the conditional evaluates correctly.

In `@src/pages/blog/`[slug].astro:
- Line 7: The message calls in src/pages/blog/[slug].astro pass
Astro.locals.locale (e.g., m.written_by({}, { locale: Astro.locals.locale }))
but the imported messages module (m from '@/services/messages') always returns
English, causing mixed-language UI labels; either remove the unused locale
argument from all message calls (m.written_by(), m.table_of_contents(),
m.see_all_from_our_blog()) so labels consistently use the module's
single-language API, or update the messages module (functions in messages.ts) to
accept the locale option and load/return the correct locale bundle (implement a
locale selector in messages.ts that reads the passed { locale } and resolves the
appropriate message file or map before returning m.written_by,
m.table_of_contents, etc.).

In `@src/pages/premium-support.astro`:
- Line 4: The page broke locale selection because the messages module (imported
as m) only loads en.json and the Proxy-created message function ignores the
second options argument (e.g., { locale }), so locale is never applied; fix
src/services/messages.ts by loading and indexing messages by locale (e.g.,
messages['en'], messages['es'], etc.) and change the Proxy-returned function to
accept both (key, options) and use options.locale (falling back to default) when
resolving the message; ensure the exported symbol used by pages (the default
export imported as m) delegates to the correct locale-specific messages map.

In `@src/pages/solutions/cordova-to-capacitor.astro`:
- Around line 345-348: The dynamic indexed lookups using m[titleKey] and
m[descKey] with `@ts-expect-error` suppressions bypass TypeScript checks and can
render raw identifiers if keys diverge; replace these dynamic accesses with a
typed accessor map so the compiler verifies keys—e.g., create a typed
dayMessages structure (e.g., dayMessages: Record<number, { title: typeof
m.solutions_cordova_to_capacitor_day1_title; desc: typeof
m.solutions_cordova_to_capacitor_day1_description }>) and use
dayMessages[day].title({}, { locale }) / dayMessages[day].desc({}, { locale })
instead of m[titleKey]/m[descKey]; update both occurrences (around
titleKey/descKey and the later lines 479–481) and remove the `@ts-expect-error`
comments so missing keys are caught at build time.

In `@src/services/messages.ts`:
- Line 25: The resolver currently returns (params?: MessageParams) =>
formatMessage(template, params) which drops the second { locale } argument;
update the returned function signature to accept the second options argument
(e.g., (params?: MessageParams, options?: { locale?: string })) and use that
locale to pick the correct localized template before calling formatMessage (or
extend/delegate to a locale-aware formatter). Specifically, in the resolver
returned closure (the function wrapping template and formatMessage), read
options.locale and either select a locale-specific template variant or pass the
locale into a locale-aware formatting helper, preserving message-level locale
selection rather than silently ignoring it.

---

Outside diff comments:
In `@src/components/doc/LanguageSelect.astro`:
- Around line 10-34: Replace the non-tabbable anchor items and hidden-focus
toggle with accessible controls: make the toggle button with
id="language-menu-button" retain visible focus (remove the "focus:outline-none"
utility), add aria-expanded and aria-controls="language-menu" and toggle its
boolean state when opening/closing the menu; render each menu item from
landingLocales as a real link with an href (e.g., locale URL) and remove
tabindex="-1" so they are keyboard-focusable, or if you must use buttons
implement full keyboard handling (Enter/Escape/Arrow navigation) for the list
with id="language-menu"; ensure the menu container has appropriate role="menu"
and each child role="menuitem" and preserve focus styles so keyboard users can
see focus.

In `@src/pages/live-update.astro`:
- Around line 948-951: SVG path elements use removed Tailwind utilities
text-opacity-40 and text-opacity-20; update the two <path> elements (the ones
with class "text-blue-500 text-opacity-40" and "text-blue-400 text-opacity-20")
to use slash opacity on the color token instead (e.g., replace "text-blue-500
text-opacity-40" with "text-blue-500/40" and "text-blue-400 text-opacity-20"
with "text-blue-400/20") so the background SVG keeps the intended opacity under
Tailwind v4.

In `@src/pages/return.astro`:
- Around line 3-7: The current calls m.return_policy({}, { locale:
Astro.locals.locale }) and m.read_our_return_policy({}, { locale:
Astro.locals.locale }) use the proxy that ignores the second-argument locale;
update these to use the messages module's explicit locale-aware API (either bind
the locale or call the resolver that takes locale as the primary argument) so
the strings are resolved for Astro.locals.locale at render time — e.g., obtain a
locale-bound messages instance or call the messages resolver with
Astro.locals.locale and replace usage in the title and description assignments
(references: m.return_policy, m.read_our_return_policy, Astro.locals.locale).

In `@src/pages/solutions/startups.astro`:
- Around line 366-369: Tailwind v4 removed the text-opacity-* utilities used on
the two <path> elements; replace the classes that use text-opacity-40 and
text-opacity-20 with the slash-opacity syntax on the color classes (e.g., change
the class list that includes "text-emerald-500 text-opacity-40" to use
"text-emerald-500/40" and similarly replace "text-teal-400 text-opacity-20" with
"text-teal-400/20"), removing the old text-opacity-* tokens so the <path>
elements render correctly under Tailwind v4.

In `@src/pages/top_cordova_app.astro`:
- Around line 91-101: The anchor hrefs in the others.map block always use
`/${l}/`, losing the current locale; update the href to call the imported
`getRelativeLocaleUrl` with the target path (e.g. call
getRelativeLocaleUrl(`/${l}/`) or similar) so links preserve locale. Locate the
map that renders anchors (the arrow function mapping `others.map((l) => ...)`),
replace the hardcoded href with the `getRelativeLocaleUrl` result, and keep
other props and the `renameCat(l)` usage unchanged.

---

Duplicate comments:
In `@astro.config.mjs`:
- Line 26: The generated path constants are producing a double slash because
SRC_DIR already ends with a slash; update the template for
GENERATED_PAGE_VERSIONS_FILE to remove the extra '/' between SRC_DIR and the
"generated/..." segment (i.e., concatenate SRC_DIR with "generated/..." without
a leading slash) and make the same fix for the other generated-path constant
referenced in the file (the similar interpolation around line 82) so no
generated paths contain a double slash.

In `@src/components/Footer.astro`:
- Around line 309-316: The language toggle button (id
"language-dropdown-button") currently removes visible focus and never exposes
dropdown state; add aria-controls="language-dropdown" to the button and add an
aria-expanded attribute that is kept in sync (true/false) whenever the dropdown
open/close logic runs (update the same place where you add/remove the "hidden"
class on the element with id "language-dropdown"); also restore a visible focus
style on the button (remove or replace the "focus:outline-none" usage with a
visible focus class such as a focus ring or outline via your CSS utility
classes) so keyboard users can see focus; apply the same changes to the other
language toggle instance referenced in the file (the block near the 347-360
region).

In `@src/components/SEO.astro`:
- Around line 51-53: The current code uses requestUrl.toString() which includes
search params (utm_*), causing dynamic landing pages to self-canonicalize with
tracking querystrings; fix by normalizing URLs to strip search params before
assigning requestedUrlString, urlString and canonicalString: create a URL object
from requestUrl (and from canonical when used), set its search to '' (and
optionally hash to '' if desired), then use its toString() (or pass that cleaned
string into toStringUrl) so that requestUrl, urlString and canonicalString are
emitted without query parameters; update the code around
requestUrl/requestedUrlString, toStringUrl(canonical) and
Astro.locals.isDynamicLandingRequest to use these cleaned URLs.

In `@src/lib/landingTranslation.ts`:
- Around line 203-220: The rewriteJsonLdValue function is currently localizing
every string (corrupting plain-text fields like name/description); modify
rewriteJsonLdValue to accept a parentKey (or key) parameter and only call
localizeInternalUrl for strings when the parent key is one of the URL-valued
JSON-LD properties (e.g., "url", "image", "sameAs", "@id", "mainEntityOfPage",
etc.), keep the existing special-case for "inLanguage" to set locale, and
propagate the current object key in recursive calls so nested URL fields are
handled correctly; update all call sites to pass an initial empty key or
undefined.
- Around line 317-341: chunkMaskedValues currently pushes an empty currentChunk
when the first entry itself exceeds the 5000-char cap and then still emits the
oversized entry; fix chunking logic in chunkMaskedValues so you never push an
empty chunk and you never emit an oversized batch: when iterating entries
compute nextLength (using entry.id and entry.maskedValue) and if nextLength >
5000 and currentChunk is empty, create a single-entry chunk with that entry and
push it (do not push an empty currentChunk first); otherwise if nextLength >
5000 and currentChunk is non-empty, push currentChunk then start a new chunk for
the entry; also ensure the max-items guard (currentChunk.length >= 24) behaves
similarly by only pushing non-empty currentChunk before starting a new one and
then adding the current entry to the new chunk, and always update currentLength
consistently.

---

Nitpick comments:
In `@src/components/landing/MonitoringFeatures.astro`:
- Around line 98-124: The SVG used for the decorative chart should be marked as
non-interactive/accessibility-hidden; update the <svg class="w-full h-full"
viewBox="0 0 100 50" preserveAspectRatio="none"> element by adding
aria-hidden="true" and focusable="false" (so assistive tech ignores it) while
leaving the paths/defs (blueGradient, greenGradient, etc.) unchanged.

In `@src/pages/consulting.astro`:
- Line 263: Replace the non-descriptive alt attribute on the <img> element that
loads "/click_and_boat.webp" (the element with class "inline-block pr-2
max-w-full align-middle border-0 w-1/8") with a meaningful description — e.g.,
"Click & Boat company logo" or "Click & Boat logo for testimonial" — so the
image conveys its purpose to assistive technologies.

In `@src/pages/solutions/production-updates.astro`:
- Around line 3-25: Add a fail-fast guard around the localized SEO strings so
missing translation fallbacks don't silently ship: after computing title and
description using m (the imported messages service) and locale, assert that
those values are not equal to the raw key or empty, and if they are, throw or
log a clear error (or call Astro.exit) so the build fails early; update the
block that defines title, description, and serviceLdJson/ldJSON to validate
m.solutions_production_updates_title and
m.solutions_production_updates_description outputs before using them in
createServiceLdJson and createLdJsonGraph.

In `@src/pages/sponsor.astro`:
- Line 120: The Layout component usage is missing the required content prop,
causing the page to lack unique SEO title/description; update the <Layout>
invocation to pass a content object with title: m.sponsor_title and description:
m.sponsor_description (e.g., content={{ title: m.sponsor_title, description:
m.sponsor_description }}), ensuring the Layout component receives the content
prop so the page has unique title and description for SEO.

In `@src/services/messages.ts`:
- Line 24: The current fallback `const template = messageTemplates[property] ??
property` exposes raw keys; change it to fail-open safely by replacing the `??
property` fallback with a clear missing-key marker and a warning log: look up
`messageTemplates[property]`, if missing call the module/logger (e.g.,
`logger.warn` or `console.warn`) with the missing key and set `template =
\`[[missing:${property}]]\`` (or throw in non-production if you prefer stricter
behavior). Update usage around the `template` variable so downstream consumers
always get either a proper template or the explicit `[[missing:...]]` sentinel
instead of the raw key.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d78a904-3fa3-4c5a-bd18-03a168485608

📥 Commits

Reviewing files that changed from the base of the PR and between 057edd9 and 3c9e549.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • src/generated/pageVersions.ts is excluded by !**/generated/**
📒 Files selected for processing (99)
  • CLAUDE.md
  • README.md
  • astro.config.mjs
  • package.json
  • src/components/AppflowShutdown.astro
  • src/components/BlogListing.astro
  • src/components/BuiltForDevelopers.astro
  • src/components/CIExpert.astro
  • src/components/Footer.astro
  • src/components/FrameworkSelector.astro
  • src/components/GetStarted.astro
  • src/components/GlobalInfrastructure.astro
  • src/components/Header.astro
  • src/components/Hero.astro
  • src/components/HowItWorks.astro
  • src/components/Manifesto.astro
  • src/components/Orgs.astro
  • src/components/ProblemSolution.astro
  • src/components/SEO.astro
  • src/components/SharedNumbers.astro
  • src/components/Testimonials.astro
  • src/components/companies-logo.astro
  • src/components/doc/CopyPage.astro
  • src/components/doc/Head.astro
  • src/components/doc/LanguageSelect.astro
  • src/components/enterprise.astro
  • src/components/landing/AutomationFeatures.astro
  • src/components/landing/DeviceLogs.astro
  • src/components/landing/MonitoringFeatures.astro
  • src/components/pricing/Calculator.astro
  • src/components/pricing/CreditPricing.astro
  • src/components/pricing/Faq.astro
  • src/components/pricing/Plans.astro
  • src/components/pricing/PriceDetails.astro
  • src/env.d.ts
  • src/lib/landingTranslation.ts
  • src/middleware.ts
  • src/pages/404.astro
  • src/pages/about.astro
  • src/pages/alternatives.astro
  • src/pages/app_mobile.astro
  • src/pages/aup.astro
  • src/pages/blog/[slug].astro
  • src/pages/bug-bounty.astro
  • src/pages/capwesome.astro
  • src/pages/consulting.astro
  • src/pages/contributing.astro
  • src/pages/disclaimer.astro
  • src/pages/dp.astro
  • src/pages/dpa.astro
  • src/pages/enterprise.astro
  • src/pages/eula.astro
  • src/pages/imprint.astro
  • src/pages/index.astro
  • src/pages/integrations.astro
  • src/pages/ionic-appflow.astro
  • src/pages/ionic-enterprise-plugins.astro
  • src/pages/live-update.astro
  • src/pages/native-build.astro
  • src/pages/plugins.astro
  • src/pages/plugins/[slug].astro
  • src/pages/premium-support.astro
  • src/pages/pricing.astro
  • src/pages/privacy.astro
  • src/pages/register.astro
  • src/pages/return.astro
  • src/pages/security.astro
  • src/pages/sla.astro
  • src/pages/solutions/agencies.astro
  • src/pages/solutions/beta-testing.astro
  • src/pages/solutions/cordova-to-capacitor-ai.astro
  • src/pages/solutions/cordova-to-capacitor.astro
  • src/pages/solutions/direct-updates.astro
  • src/pages/solutions/ecommerce.astro
  • src/pages/solutions/fintech.astro
  • src/pages/solutions/healthcare.astro
  • src/pages/solutions/ionic-enterprise-plugins.astro
  • src/pages/solutions/pr-preview.astro
  • src/pages/solutions/production-updates.astro
  • src/pages/solutions/qsr.astro
  • src/pages/solutions/solo-developers.astro
  • src/pages/solutions/startups.astro
  • src/pages/solutions/version-targeting.astro
  • src/pages/solutions/white-label.astro
  • src/pages/sponsor.astro
  • src/pages/subprocessors.astro
  • src/pages/support-policy.astro
  • src/pages/top_app.astro
  • src/pages/top_capacitor_app.astro
  • src/pages/top_capgo_app.astro
  • src/pages/top_cordova_app.astro
  • src/pages/top_flutter_app.astro
  • src/pages/top_kotlin_app.astro
  • src/pages/top_native_script_app.astro
  • src/pages/top_react_native_app.astro
  • src/pages/tos.astro
  • src/pages/trust.astro
  • src/services/landingLocale.ts
  • src/services/messages.ts
✅ Files skipped from review due to trivial changes (4)
  • src/components/FrameworkSelector.astro
  • package.json
  • CLAUDE.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/env.d.ts
  • src/middleware.ts

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62ced16fa2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/doc/LanguageSelect.astro`:
- Around line 28-35: The locale items rendered in LanguageSelect.astro via
availableLocales.map (the anonymous map callback that currently outputs <a ...
tabindex="-1">) are not keyboard-accessible and the dropdown trigger never
exposes an expanded state; change each locale element to a real interactive
control (either an <a> with a valid href to the language URL or a <button> if it
performs a JS-only switch) and remove tabindex="-1" so they remain in the
natural tab order, ensure they have appropriate role/aria attributes (e.g.,
role="menuitem" remains OK for menu semantics), and update the dropdown trigger
component/state to expose aria-expanded={isOpen} (wire the existing toggle state
used by the trigger) so screen readers know when the menu is open; apply the
same fixes to the other menu rendering block referenced in lines 42-80.

In `@src/lib/landingTranslation.ts`:
- Around line 6-10: Translated pages still serve source-language JSON-LD because
SCRIPT is in SKIP_TAGS and rewriteStructuredDataUrls() only updates URL and
inLanguage fields; update landingTranslation logic to either pass JSON-LD
strings/objects through the same segment translation pipeline or suppress
JSON-LD when a translation is applied. Locate SKIP_TAGS, JSON_LD_URL_KEYS and
the function rewriteStructuredDataUrls() in src/lib/landingTranslation.ts, and:
(1) when encountering <script type="application/ld+json"> extract and parse the
JSON-LD, feed its text/string fields (and any properties beyond JSON_LD_URL_KEYS
that contain human-readable text) into the segment translator, then reserialize
and replace the script content; or (2) if that is not feasible, detect when a
page is translated and remove or disable inclusion of JSON-LD output, and
instead use the centralized src/lib/ldJson.ts helpers to emit localized
structured data from the SEO component so JSON-LD matches translated meta/HTML.
Ensure rewriteStructuredDataUrls() still updates URL keys and that any
inLanguage property is set to the target locale.

In `@src/middleware.ts`:
- Around line 172-178: createCacheKey(requestUrl: URL, siteBuildVersion and
pageVersion) currently uses the raw request URL so unrelated query params
(utm_*, cache busters, preview tokens) create unique cache entries; change
createCacheKey to normalize the URL by clearing requestUrl.search and then
re-adding only a small whitelist of content-affecting params (e.g., lang,
locale, preview (if used for content), or other explicit keys your app relies
on), then set __capgo_build and __capgo_page and return the Request; reference
the createCacheKey function and the requestUrl variable when making this change
so only meaningful params affect the cache key.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92f02016-f62a-45d3-9b0a-f670f5af34fa

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9e549 and 62ced16.

⛔ Files ignored due to path filters (1)
  • src/generated/pageVersions.ts is excluded by !**/generated/**
📒 Files selected for processing (7)
  • astro.config.mjs
  • src/components/Footer.astro
  • src/components/SEO.astro
  • src/components/doc/LanguageSelect.astro
  • src/lib/landingTranslation.ts
  • src/middleware.ts
  • src/services/landingLocale.ts
✅ Files skipped from review due to trivial changes (1)
  • astro.config.mjs

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: 2

🧹 Nitpick comments (1)
src/middleware.ts (1)

54-58: Remove unnecessary type assertion.

SonarCloud correctly identifies that routeLocale as Locales is redundant since routeLocale already has a compatible type from the conditional assignment. The assertion adds no value and reduces type safety.

♻️ Suggested fix
   const routeLocale = isStaticLocale(requestedRoute.locale) ? requestedRoute.locale : defaultLocale
-  context.locals.locale = routeLocale as Locales
+  context.locals.locale = routeLocale
   context.locals.displayLocale = routeLocale
   context.locals.requestedLocale = routeLocale
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware.ts` around lines 54 - 58, The type assertion on routeLocale is
unnecessary: remove "as Locales" and assign routeLocale directly to
context.locals.locale (i.e., replace context.locals.locale = routeLocale as
Locales with context.locals.locale = routeLocale). Ensure the surrounding logic
using isStaticLocale(requestedRoute.locale) and defaultLocale remains unchanged
so routeLocale retains a compatible type for context.locals.locale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/landingTranslation.ts`:
- Around line 416-474: The nested json_schema passed in translateChunk (the
schema constant used in response_format.json_schema) can be too strict for some
models; update translateChunk to (1) catch schema validation failures from
ai.run (detect messages/errors like "JSON Mode couldn't be met" or a
null/invalid parsed result) and retry with a simplified schema or without
json_schema, (2) simplify the schema used in response_format by flattening to a
single object map (e.g., a translations: { additionalProperties: { type:
"string" } } or removing additionalProperties: false and the per-key required
list) so the model has less strict validation, and (3) keep using
extractTranslations to parse final output but ensure the retry path feeds
extractTranslations the same shape. Reference translateChunk, schema,
response_format, ai.run, and extractTranslations when locating code to implement
these changes.

In `@src/services/messages.ts`:
- Around line 31-36: The current formatMessage function will call String(value)
and render objects/arrays as “[object Object]”; update formatMessage to detect
non-primitive values for placeholders (e.g., where typeof value === 'object' and
value !== null or Array.isArray(value)) and serialize them safely (use
JSON.stringify with try/catch to handle circular refs and fall back to a safe
representation) before returning; keep the existing behavior for primitives and
null/undefined (return `{key}` for undefined/null), and reference formatMessage,
MessageParams and placeholderPattern when making the change.

---

Nitpick comments:
In `@src/middleware.ts`:
- Around line 54-58: The type assertion on routeLocale is unnecessary: remove
"as Locales" and assign routeLocale directly to context.locals.locale (i.e.,
replace context.locals.locale = routeLocale as Locales with
context.locals.locale = routeLocale). Ensure the surrounding logic using
isStaticLocale(requestedRoute.locale) and defaultLocale remains unchanged so
routeLocale retains a compatible type for context.locals.locale.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4eee5658-c080-4f4e-9fd4-cc41c5248606

📥 Commits

Reviewing files that changed from the base of the PR and between 62ced16 and 48d9e92.

⛔ Files ignored due to path filters (1)
  • src/generated/pageVersions.ts is excluded by !**/generated/**
📒 Files selected for processing (11)
  • seo-checker.config.json
  • src/components/Footer.astro
  • src/components/SEO.astro
  • src/components/landing/DeviceLogs.astro
  • src/lib/landingTranslation.ts
  • src/middleware.ts
  • src/pages/aup.astro
  • src/pages/disclaimer.astro
  • src/pages/solutions/cordova-to-capacitor.astro
  • src/pages/tos.astro
  • src/services/messages.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/landing/DeviceLogs.astro
  • src/pages/aup.astro

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3eb3100461

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 89 to 91
if (entry.data.author) content['author'] = entry.data.author || 'Capgo'
if (entry.data.keywords) content['keywords'] = entry.data.keywords.split(',') || []
if (alternateVersions.length > 0) content['alternateVersions'] = alternateVersions

// Add article-specific OG tags for better social sharing
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore blog-specific hreflang variants

This change drops the blog page’s explicit alternateVersions override, so SEO.astro falls back to path-based alternates for every static locale even when a post only exists in one or a few locales. For example, src/content/blog/id/building-a-native-mobile-app-with-nuxt-3-and-capacitor.md has no sibling entries, but the rendered page will still advertise de/es/fr/... hreflang URLs that resolve to 404s, which can hurt indexing quality and language targeting; the blog route should keep deriving alternates from actual localized entries for that post ID.

Useful? React with 👍 / 👎.

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.

1 participant