Sanity#119
Conversation
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThe pull request introduces comprehensive localization support across the blog system. Frontend components (PostDetails, blog pages, cards) now accept a locale parameter and fetch locale-specific content with fallbacks to English. Sanity studio schemas for author and post are restructured to store content as multilingual objects (en, pl, uk) instead of single strings. Translation files are updated with new keys and removed arrow symbols from resource links. Changes
Sequence DiagramsequenceDiagram
actor User
participant Page as page.tsx<br/>[locale]/blog/[slug]
participant PostDetails as PostDetails.tsx
participant Sanity as Sanity GROQ
participant Trans as Translations
User->>Page: Request blog post
Note over Page: Extract params: { slug, locale }
Page->>Sanity: Fetch post<br/>query with coalesce(<br/>title[$locale],<br/>title.en, title)
Sanity-->>Page: Return post with<br/>localized title, body,<br/>author fields
Page->>PostDetails: Pass slug + locale
PostDetails->>Trans: getTranslations('blog')
Trans-->>PostDetails: t function
PostDetails->>PostDetails: Resolve body[$locale]<br/>Render author section<br/>with localized fields
PostDetails-->>User: Render localized<br/>post + translations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)
145-165: AvoidMath.random()as React key and consider using next/image for body images.Two concerns with body rendering:
Math.random()as a fallback key (line 150) breaks React's reconciliation and can cause unnecessary re-renders. Use a stable identifier like the block index.The
<img>tag for body images bypasses Next.js image optimization. Consider usingnext/imagewith appropriate sizing.🔧 Suggested fix for key and image optimization
- {post.body?.map((block: any) => { + {post.body?.map((block: any, index: number) => { if (block?._type === 'block') { const text = (block.children || []) .map((c: any) => c.text || '') .join(''); - return <p key={block._key || Math.random()}>{text}</p>; + return <p key={block._key || `block-${index}`}>{text}</p>; } if (block?._type === 'image' && block?.url) { return ( - <img - key={block._key || block.url} + <Image + key={block._key || `image-${index}`} src={block.url} alt={post.title || 'Post image'} + width={800} + height={450} className="rounded-xl border border-gray-200 my-6" /> ); }
🤖 Fix all issues with AI agents
In @frontend/app/[locale]/blog/page.tsx:
- Around line 28-64: The GROQ coalesce fallbacks are returning whole objects as
the third argument; update all coalesce calls in the query so scalar/locale
fallbacks only use the locale and English keys (remove the third fallback that
references the object). Specifically, change coalesce(title[$locale], title.en,
title) → coalesce(title[$locale], title.en), coalesce(body[$locale], body.en,
body)[] → coalesce(body[$locale], body.en)[], and likewise for all author
subfields (coalesce(name[$locale], name.en, name) → coalesce(name[$locale],
name.en), coalesce(company[$locale], company.en, company) →
coalesce(company[$locale], company.en), coalesce(jobTitle[$locale], jobTitle.en,
jobTitle) → coalesce(jobTitle[$locale], jobTitle.en), coalesce(city[$locale],
city.en, city) → coalesce(city[$locale], city.en), coalesce(bio[$locale],
bio.en, bio) → coalesce(bio[$locale], bio.en)); ensure you only remove the third
argument and keep the array-unpacking ([]) on body as shown.
🧹 Nitpick comments (5)
frontend/components/blog/BlogFilters.tsx (1)
55-68: LGTM! Clear documentation added.The JSDoc comment accurately describes the normalization behavior, and the function implementation matches the documentation. Exporting this utility enables reuse across other components.
📝 Optional: Enhance JSDoc with type annotations
For improved IDE intellisense and auto-completion, consider adding
@paramand@returnstags:/** * Normalize a tag/search input: * - removes leading "#" * - trims spaces * - lowercases * - collapses multiple spaces + * @param input - The tag string to normalize + * @returns The normalized tag string */ export function normalizeTag(input: string) {studio/schemaTypes/post.ts (1)
8-17: Localization structure looks good.The multilingual object pattern for title with en/pl/uk subfields is well-structured. Consider adding validation to ensure at least the English field is required, as the slug generation depends on it.
💡 Optional: Add validation for required English title
defineField({ name: 'title', title: 'Title', type: 'object', fields: [ - defineField({name: 'en', title: 'English', type: 'string'}), + defineField({name: 'en', title: 'English', type: 'string', validation: (Rule) => Rule.required()}), defineField({name: 'pl', title: 'Polish', type: 'string'}), defineField({name: 'uk', title: 'Ukrainian', type: 'string'}), ], }),frontend/components/blog/BlogCard.tsx (1)
111-122: Add explicit button type for accessibility.Buttons inside forms default to
type="submit". While this isn't in a form, explicitly settingtype="button"is a best practice to prevent unexpected behavior if the component is later used within a form context.💡 Suggested improvement
<button key={`${norm}-${i}`} + type="button" onClick={() => onTagToggle(norm)}studio/schemaTypes/author.ts (1)
8-17: Consistent multilingual pattern across author fields.The localization structure for name, company, jobTitle, and city follows the same pattern as the post schema. This consistency is good for maintainability.
Consider extracting a reusable helper for the multilingual string pattern to reduce repetition:
// Example helper (could be in a shared utils file) const localizedString = (name: string, title: string) => defineField({ name, title, type: 'object', fields: [ defineField({name: 'en', title: 'English', type: 'string'}), defineField({name: 'pl', title: 'Polish', type: 'string'}), defineField({name: 'uk', title: 'Ukrainian', type: 'string'}), ], });Also applies to: 82-111
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (1)
13-32: Type definitions could be more precise.The types correctly mark fields as optional, which aligns with the GROQ coalesce fallback pattern. Consider using more specific types for
bioandbody(e.g.,PortableTextBlock[]) instead ofanyfor better type safety.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonstudio/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
frontend/app/[locale]/blog/[slug]/PostDetails.tsxfrontend/app/[locale]/blog/[slug]/page.tsxfrontend/app/[locale]/blog/page.tsxfrontend/components/blog/AuthorModal.tsxfrontend/components/blog/BlogCard.tsxfrontend/components/blog/BlogFilters.tsxfrontend/components/blog/BlogGrid.tsxfrontend/messages/en.jsonfrontend/messages/pl.jsonfrontend/messages/uk.jsonstudio/schemaTypes/author.tsstudio/schemaTypes/post.ts
🔇 Additional comments (18)
frontend/components/blog/BlogGrid.tsx (1)
23-23: LGTM! Improved spacing for blog grid.The increased gap from
gap-10togap-12(40px → 48px) provides better visual separation between blog cards and accommodates the richer content blocks introduced by the localization changes. The adjustment aligns well with the PR's styling objectives.frontend/components/blog/BlogFilters.tsx (1)
86-91: LGTM! Component documentation enhances maintainability.The JSDoc clearly describes the component's key features, particularly the AND filtering semantics and Tab-to-accept autocomplete UX, which are valuable details for future maintainers.
frontend/messages/pl.json (1)
54-56: LGTM! Translation updates are consistent.The removal of the arrow symbol from
visitResourceand the addition of the new keysreadArticleandaboutAuthoralign with the broader localization effort across all locale files.frontend/messages/uk.json (1)
54-56: LGTM! Translation updates match other locales.The Ukrainian translations are consistent with the changes in the English and Polish locale files, properly supporting the blog localization feature.
frontend/messages/en.json (1)
54-56: LGTM! Base translations established correctly.The English translations serve as the fallback locale in the coalesce pattern used throughout the blog localization. The new keys
readArticleandaboutAuthorare properly defined.frontend/components/blog/AuthorModal.tsx (4)
113-114: Good defensive programming with fallback handling.The fallback to
'Unknown author'prevents display issues when author name is missing. The conditionalmetaTextformatting appropriately handles both cases (with and without date).
144-146: LGTM! Trigger button simplified with better hover states.The updated styling improves user experience with clear hover feedback, and simplifying the trigger to display
metaTextmakes the component more maintainable.
181-181: Minor alignment improvement.The
-mt-pxadjustment ensures the close button glyph is properly centered.
228-228: Visual separation improved for social links section.Adding a top border and padding properly delineates the social links from the author bio content.
frontend/app/[locale]/blog/page.tsx (1)
6-26: Async params handling is correct for Next.js 15+.The use of
Promise<{ locale: string }>for params and awaiting it before use aligns with Next.js 15's async params pattern. The locale is correctly propagated to both translation and data fetching functions.studio/schemaTypes/post.ts (1)
76-100: Body localization structure is consistent.The multilingual body field correctly maintains the array of blocks/images per language, consistent with the title field pattern.
frontend/components/blog/BlogCard.tsx (2)
19-26: Excerpt extraction is well-implemented.The null-safe handling with
post.body ?? []and type guards for filtering blocks is robust. The nested children traversal correctly handles the PortableTextSpan type.
44-63: Image link implementation looks good.The image is correctly wrapped in a Link using
post.slug.current. The grayscale/brightness styling provides a nice visual effect. Ensure that the parent component passes the locale-resolvedpost.titlestring for proper alt text.studio/schemaTypes/author.ts (1)
35-79: Bio localization structure is intentionally simplified.The bio field correctly uses arrays of blocks per language with limited styling (normal only, no lists). This is appropriate for author bios which typically don't need complex formatting.
frontend/app/[locale]/blog/[slug]/page.tsx (2)
22-25: GROQ locale fallback chain is reasonable.The
coalesce(title[$locale], title.en, title)pattern provides good fallbacks. Note that the finaltitlefallback would return the entire object if both locale-specific and English fields are missing—though this edge case is unlikely if content validation is enforced in Sanity.
5-13: The current implementation is correct. The parent[locale]/layout.tsxhasexport const dynamic = 'force-dynamic', which forces all child pages in the[locale]subtree to render dynamically. This meansgenerateStaticParamsat the child level is never executed, and the suggested fix to add locale variants is unnecessary. The page correctly receives bothslugandlocalefrom the route parameters and uses them ingenerateMetadataand the page component.Likely an incorrect or invalid review comment.
frontend/app/[locale]/blog/[slug]/PostDetails.tsx (2)
72-86: Locale-aware data fetching is well-implemented.The component correctly accepts and uses the
localeprop for both translations and GROQ queries. The fallback chain in the query ensures graceful degradation when translations are missing.
181-212: Author section implementation is solid.The conditional rendering logic, translated heading, and metadata composition are well-implemented. The author image correctly uses
next/imagewith proper sizing and styling.
| const posts = await client.fetch( | ||
| groq` | ||
| *[_type == "post" && defined(slug.current)] | ||
| | order(publishedAt desc) { | ||
| _id, | ||
| "title": coalesce(title[$locale], title.en, title), | ||
| slug, | ||
| publishedAt, | ||
| tags, | ||
| resourceLink, | ||
|
|
||
| "categories": categories[]->title, | ||
| "categories": categories[]->title, | ||
|
|
||
| body[] { | ||
| ..., | ||
| children[]{ | ||
| text | ||
| } | ||
| }, | ||
| "mainImage": mainImage.asset->url, | ||
| "body": coalesce(body[$locale], body.en, body)[]{ | ||
| ..., | ||
| children[]{ | ||
| text | ||
| } | ||
| }, | ||
| "mainImage": mainImage.asset->url, | ||
| "author": author->{ | ||
| name, | ||
| company, | ||
| jobTitle, | ||
| city, | ||
| bio, | ||
| "name": coalesce(name[$locale], name.en, name), | ||
| "company": coalesce(company[$locale], company.en, company), | ||
| "jobTitle": coalesce(jobTitle[$locale], jobTitle.en, jobTitle), | ||
| "city": coalesce(city[$locale], city.en, city), | ||
| "bio": coalesce(bio[$locale], bio.en, bio), | ||
| "image": image.asset->url, | ||
| socialMedia[]{ | ||
| _key, | ||
| platform, | ||
| url | ||
| platform, | ||
| url | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `); | ||
| `, | ||
| { locale } | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for Sanity schema files and multilingual field definitions
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) -path "*/schema*" -o -path "*/schemaTypes*" | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 533
🏁 Script executed:
# Look for schema type definitions, especially for post and author
fd -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs rg -l "defineType|defineField|schema" | grep -i schema | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 495
🏁 Script executed:
# Search for post and author type definitions
rg "defineType.*post|defineType.*author" -A 20 | head -100Repository: DevLoversTeam/devlovers.net
Length of output: 53
🏁 Script executed:
# Check directory structure for typical Sanity project layout
ls -la | head -20Repository: DevLoversTeam/devlovers.net
Length of output: 1089
🏁 Script executed:
wc -l studio/schemaTypes/post.ts studio/schemaTypes/author.tsRepository: DevLoversTeam/devlovers.net
Length of output: 147
🏁 Script executed:
cat -n studio/schemaTypes/post.tsRepository: DevLoversTeam/devlovers.net
Length of output: 3420
🏁 Script executed:
cat -n studio/schemaTypes/author.tsRepository: DevLoversTeam/devlovers.net
Length of output: 4061
Schema changes are in place, but fix the coalesce fallback patterns.
The Sanity schema files have been correctly updated with multilingual object-based fields (en, pl, uk) for title, body, and all author fields, which aligns with the GROQ implementation.
However, the coalesce fallback patterns have a critical bug: the third fallback option should be removed. Currently:
coalesce(title[$locale], title.en, title)→ the fallback totitlereturns the entire object instead of a stringcoalesce(body[$locale], body.en, body)[]→ applying[]to an object breaks the iteration
Fix by removing the problematic third fallback:
- Scalar fields:
coalesce(title[$locale], title.en) - Array fields:
coalesce(body[$locale], body.en)[]
This ensures the query gracefully handles missing locales by falling back to English without attempting to return object references.
🤖 Prompt for AI Agents
In @frontend/app/[locale]/blog/page.tsx around lines 28 - 64, The GROQ coalesce
fallbacks are returning whole objects as the third argument; update all coalesce
calls in the query so scalar/locale fallbacks only use the locale and English
keys (remove the third fallback that references the object). Specifically,
change coalesce(title[$locale], title.en, title) → coalesce(title[$locale],
title.en), coalesce(body[$locale], body.en, body)[] → coalesce(body[$locale],
body.en)[], and likewise for all author subfields (coalesce(name[$locale],
name.en, name) → coalesce(name[$locale], name.en), coalesce(company[$locale],
company.en, company) → coalesce(company[$locale], company.en),
coalesce(jobTitle[$locale], jobTitle.en, jobTitle) → coalesce(jobTitle[$locale],
jobTitle.en), coalesce(city[$locale], city.en, city) → coalesce(city[$locale],
city.en), coalesce(bio[$locale], bio.en, bio) → coalesce(bio[$locale], bio.en));
ensure you only remove the third argument and keep the array-unpacking ([]) on
body as shown.
Adding styles to the blog page
Summary by CodeRabbit
New Features
Design & UI
Localization
✏️ Tip: You can customize this high-level summary in your review settings.