[codex] Add Cloudflare AI landing translation layer#550
[codex] Add Cloudflare AI landing translation layer#550
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
There was a problem hiding this comment.
💡 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".
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRemoves 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/lib/alternateVersions.ts (1)
1-1: Unused import:defaultLocaleis imported but never used.The
defaultLocaleimport from@/services/localeis 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/inDYNAMIC_LANDING_EXACT_EXCLUDES, but line 69 also has/docs/inDYNAMIC_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.
writeGeneratedPageVersionModuleis called at module scope during config evaluation. This means:
- Every
astro devrestart regenerates with a freshsiteBuildVersiontimestamp- The file change could trigger file watchers, potentially causing rebuild loops
- Timestamp drifts from actual build completion time
Consider using an Astro integration hook like
astro:build:startto 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
normalizePathnamefunction 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
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.locksrc/generated/pageVersions.tsis excluded by!**/generated/**
📒 Files selected for processing (11)
astro.config.mjspackage.jsonsrc/components/Footer.astrosrc/components/SEO.astrosrc/env.d.tssrc/layouts/Layout.astrosrc/lib/alternateVersions.tssrc/lib/landingTranslation.tssrc/middleware.tssrc/services/landingLocale.tswrangler.jsonc
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.locksrc/generated/pageVersions.tsis excluded by!**/generated/**
📒 Files selected for processing (3)
astro.config.mjspackage.jsonwrangler.jsonc
✅ Files skipped from review due to trivial changes (2)
- package.json
- wrangler.jsonc
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
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 (persrc/services/messages.tsproxy behavior). That makestitle/descriptionand 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 | 🟡 MinorReplace removed
text-opacity-*utilities with slash notation.
text-opacity-40andtext-opacity-20were 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 | 🟠 MajorPreserve 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 importedgetRelativeLocaleUrlhelper 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 | 🟡 MinorReplace 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 | 🟠 MajorUse real, tabbable links for the locale menu.
These options are rendered as anchors without
hrefand withtabindex="-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 syncaria-expanded/aria-controls.As per coding guidelines, "All interactive elements must be keyboard accessible", "Focus states must be visible", and "Add
aria-expandedto 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 | 🟠 MajorOnly localize URL fields inside JSON-LD.
The string branch runs every JSON-LD string through
localizeInternalUrl(). Plain-text fields likename,description, orheadlinethen 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.tshelpers 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 | 🟠 MajorDon'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 | 🟠 MajorStrip search params before emitting canonical/social URLs.
requestUrl.toString()still carries tracking/search params. On dynamic landing requests those values flow into bothcanonicalStringandurlString, 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 | 🟠 MajorExpose 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. Addaria-controls/aria-expanded, keep the attribute in sync in both handlers, and restore a visible focus style.As per coding guidelines, "Add
aria-expandedto 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 | 🟡 MinorDrop the redundant
/when composing generated paths.
SRC_DIRalready 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,
?? propertysilently 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/messagesfalls 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: Missingcontentprop on Layout component.This page uses
m.sponsor_titleandm.sponsor_descriptionfor display but doesn't pass acontentobject to the Layout component. Per coding guidelines, pages must have uniquetitleanddescriptionin 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
titleanddescriptionin 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
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.locksrc/generated/pageVersions.tsis excluded by!**/generated/**
📒 Files selected for processing (99)
CLAUDE.mdREADME.mdastro.config.mjspackage.jsonsrc/components/AppflowShutdown.astrosrc/components/BlogListing.astrosrc/components/BuiltForDevelopers.astrosrc/components/CIExpert.astrosrc/components/Footer.astrosrc/components/FrameworkSelector.astrosrc/components/GetStarted.astrosrc/components/GlobalInfrastructure.astrosrc/components/Header.astrosrc/components/Hero.astrosrc/components/HowItWorks.astrosrc/components/Manifesto.astrosrc/components/Orgs.astrosrc/components/ProblemSolution.astrosrc/components/SEO.astrosrc/components/SharedNumbers.astrosrc/components/Testimonials.astrosrc/components/companies-logo.astrosrc/components/doc/CopyPage.astrosrc/components/doc/Head.astrosrc/components/doc/LanguageSelect.astrosrc/components/enterprise.astrosrc/components/landing/AutomationFeatures.astrosrc/components/landing/DeviceLogs.astrosrc/components/landing/MonitoringFeatures.astrosrc/components/pricing/Calculator.astrosrc/components/pricing/CreditPricing.astrosrc/components/pricing/Faq.astrosrc/components/pricing/Plans.astrosrc/components/pricing/PriceDetails.astrosrc/env.d.tssrc/lib/landingTranslation.tssrc/middleware.tssrc/pages/404.astrosrc/pages/about.astrosrc/pages/alternatives.astrosrc/pages/app_mobile.astrosrc/pages/aup.astrosrc/pages/blog/[slug].astrosrc/pages/bug-bounty.astrosrc/pages/capwesome.astrosrc/pages/consulting.astrosrc/pages/contributing.astrosrc/pages/disclaimer.astrosrc/pages/dp.astrosrc/pages/dpa.astrosrc/pages/enterprise.astrosrc/pages/eula.astrosrc/pages/imprint.astrosrc/pages/index.astrosrc/pages/integrations.astrosrc/pages/ionic-appflow.astrosrc/pages/ionic-enterprise-plugins.astrosrc/pages/live-update.astrosrc/pages/native-build.astrosrc/pages/plugins.astrosrc/pages/plugins/[slug].astrosrc/pages/premium-support.astrosrc/pages/pricing.astrosrc/pages/privacy.astrosrc/pages/register.astrosrc/pages/return.astrosrc/pages/security.astrosrc/pages/sla.astrosrc/pages/solutions/agencies.astrosrc/pages/solutions/beta-testing.astrosrc/pages/solutions/cordova-to-capacitor-ai.astrosrc/pages/solutions/cordova-to-capacitor.astrosrc/pages/solutions/direct-updates.astrosrc/pages/solutions/ecommerce.astrosrc/pages/solutions/fintech.astrosrc/pages/solutions/healthcare.astrosrc/pages/solutions/ionic-enterprise-plugins.astrosrc/pages/solutions/pr-preview.astrosrc/pages/solutions/production-updates.astrosrc/pages/solutions/qsr.astrosrc/pages/solutions/solo-developers.astrosrc/pages/solutions/startups.astrosrc/pages/solutions/version-targeting.astrosrc/pages/solutions/white-label.astrosrc/pages/sponsor.astrosrc/pages/subprocessors.astrosrc/pages/support-policy.astrosrc/pages/top_app.astrosrc/pages/top_capacitor_app.astrosrc/pages/top_capgo_app.astrosrc/pages/top_cordova_app.astrosrc/pages/top_flutter_app.astrosrc/pages/top_kotlin_app.astrosrc/pages/top_native_script_app.astrosrc/pages/top_react_native_app.astrosrc/pages/tos.astrosrc/pages/trust.astrosrc/services/landingLocale.tssrc/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
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
src/generated/pageVersions.tsis excluded by!**/generated/**
📒 Files selected for processing (7)
astro.config.mjssrc/components/Footer.astrosrc/components/SEO.astrosrc/components/doc/LanguageSelect.astrosrc/lib/landingTranslation.tssrc/middleware.tssrc/services/landingLocale.ts
✅ Files skipped from review due to trivial changes (1)
- astro.config.mjs
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/middleware.ts (1)
54-58: Remove unnecessary type assertion.SonarCloud correctly identifies that
routeLocale as Localesis redundant sincerouteLocalealready 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
⛔ Files ignored due to path filters (1)
src/generated/pageVersions.tsis excluded by!**/generated/**
📒 Files selected for processing (11)
seo-checker.config.jsonsrc/components/Footer.astrosrc/components/SEO.astrosrc/components/landing/DeviceLogs.astrosrc/lib/landingTranslation.tssrc/middleware.tssrc/pages/aup.astrosrc/pages/disclaimer.astrosrc/pages/solutions/cordova-to-capacitor.astrosrc/pages/tos.astrosrc/services/messages.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/landing/DeviceLogs.astro
- src/pages/aup.astro
|
There was a problem hiding this comment.
💡 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".
| 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 |
There was a problem hiding this comment.
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 👍 / 👎.



What changed
Why
Validation
Known blockers outside this PR
astro-heroiconsimport failures insrc/config/plugins.ts.account_idfor non-interactive local builds.Summary by CodeRabbit
New Features
Improvements
Chores