Skip to content

Conversation

@bmanquen
Copy link
Collaborator

@bmanquen bmanquen commented Feb 2, 2026

Dark Mode:
image
image

Light Mode:
image
image

@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@changeset-bot
Copy link

changeset-bot bot commented Feb 2, 2026

🦋 Changeset detected

Latest commit: 722d236

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-react-hooks Minor
@youversion/platform-react-ui Minor
@youversion/platform-core Minor
nextjs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 2, 2026

Greptile Overview

Greptile Summary

This PR implements intelligent language suggestion in the Bible version picker by auto-detecting user preferences from browser settings and regional context.

Key Changes:

  • Replaced hardcoded English default with automatic language detection from navigator.languages[0]
  • Added tabbed interface with "Suggested" and "All" tabs for language selection
  • Suggested languages combine user's browser language preferences with country-based languages (sorted by speaking population)
  • Fetches complete language data including display_names field for proper internationalization
  • Updated UI components (tabs.tsx, item.tsx) with minor styling improvements for consistency
  • Added comprehensive integration test in Storybook with proper navigator.languages mocking
  • Enhanced MSW handlers to support country-based language filtering (country=zz for IP-based detection)

Implementation Details:

  • User languages are extracted from navigator.languages, preserving browser preference order
  • Country languages are fetched via API with country=zz parameter and sorted by speaking population
  • Deduplication ensures languages appear only once, prioritizing user preferences
  • SSR-safe with typeof navigator !== 'undefined' checks throughout

Confidence Score: 4.5/5

  • This PR is safe to merge with very low risk
  • The implementation is well-tested with comprehensive integration tests, includes proper SSR safety checks, and follows established patterns. One minor style issue with unnecessary optional chaining exists but doesn't affect functionality. The feature enhances UX by intelligently detecting user language preferences.
  • No files require special attention - the optional chaining style issue in bible-version-picker.tsx:267 is trivial

Important Files Changed

Filename Overview
.changeset/brave-lions-sort.md Proper changeset entry for minor version bump with detailed feature description
packages/ui/src/components/bible-version-picker.tsx Implemented suggested languages with browser locale detection and regional filtering - has one minor logical issue with undefined values
packages/ui/src/components/bible-version-picker.stories.tsx Added comprehensive integration test for suggested languages feature with proper mocking
packages/ui/src/test/mocks/handlers.ts Updated MSW handlers to support language filtering by country for testing

Sequence Diagram

sequenceDiagram
    participant User
    participant BibleVersionPicker
    participant Navigator
    participant useLanguages
    participant useVersions
    participant API

    User->>BibleVersionPicker: Open picker
    BibleVersionPicker->>Navigator: Check navigator.languages
    Navigator-->>BibleVersionPicker: ['en-US', 'ko-KR']
    
    BibleVersionPicker->>useLanguages: Fetch all languages
    useLanguages->>API: GET /languages?fields=id,display_names,speaking_population&page_size=*
    API-->>useLanguages: Language list
    useLanguages-->>BibleVersionPicker: languages.data
    
    BibleVersionPicker->>useLanguages: Fetch country languages (country=zz)
    useLanguages->>API: GET /languages?country=zz&fields=id,display_names&page_size=*
    API-->>useLanguages: Country-specific languages
    useLanguages-->>BibleVersionPicker: countryLanguages.data
    
    BibleVersionPicker->>useVersions: Fetch versions by language
    useVersions->>API: GET /versions?language_tag={selected}
    API-->>useVersions: Bible versions
    useVersions-->>BibleVersionPicker: versions.data
    
    BibleVersionPicker->>BibleVersionPicker: Calculate suggestedLanguages
    Note over BibleVersionPicker: 1. Extract browser language codes<br/>2. Match with available languages<br/>3. Add country languages by population<br/>4. Deduplicate by language ID
    
    BibleVersionPicker->>User: Display Suggested tab with<br/>user + country languages
    
    User->>BibleVersionPicker: Switch to All tab
    BibleVersionPicker->>User: Display all languages (alphabetically)
    
    User->>BibleVersionPicker: Select language
    BibleVersionPicker->>useVersions: Fetch versions for new language
    useVersions->>API: GET /versions?language_tag={new_language}
    API-->>useVersions: Bible versions
    useVersions-->>BibleVersionPicker: versions.data
    BibleVersionPicker->>User: Display versions in selected language
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

camrun91
camrun91 previously approved these changes Feb 2, 2026
Copy link
Collaborator

@camrun91 camrun91 left a comment

Choose a reason for hiding this comment

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

this looks good to me.

@bmanquen
Copy link
Collaborator Author

bmanquen commented Feb 3, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

className="yv:h-5 yv:min-w-5 yv:rounded-full yv:px-1 yv:font-mono yv:tabular-nums"
>
{filteredVersions.length}
{filteredVersions.length + filteredRecentVersions.length}
Copy link
Contributor

Choose a reason for hiding this comment

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

Badge count logic appears incorrect - it adds filteredRecentVersions.length which represents recent versions that match the search query, but these are already included in the displayed list. This could lead to double-counting. The badge should show the actual count of items displayed in the versions list.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/components/bible-version-picker.tsx
Line: 410:410

Comment:
Badge count logic appears incorrect - it adds `filteredRecentVersions.length` which represents recent versions that match the search query, but these are already included in the displayed list. This could lead to double-counting. The badge should show the actual count of items displayed in the versions list.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not true because recently used versions get filtered out of filteredVersions.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're absolutely right. Looking at the code, I can see that filteredVersions would be filtered to exclude the recent versions that are passed to the useFilteredVersions hook (lines 189-194), so there's no double-counting issue. The badge correctly shows the total count of both the filtered recent versions and the filtered regular versions that are displayed in the UI. Thanks for the correction!

Comment on lines 266 to 268
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
(code) => code.split('-')[0]?.toLowerCase() || '',
);
Copy link
Contributor

Choose a reason for hiding this comment

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

The .map() on navigator.languages could produce an array with empty strings if split('-')[0] returns undefined. While the || '' handles undefined, it still adds empty strings to the array which are then filtered later. Consider using .filter(Boolean) after the map to remove empty strings.

Suggested change
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
(code) => code.split('-')[0]?.toLowerCase() || '',
);
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : [])
.map((code) => code.split('-')[0]?.toLowerCase())
.filter((code): code is string => Boolean(code));

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/components/bible-version-picker.tsx
Line: 266:268

Comment:
The `.map()` on `navigator.languages` could produce an array with empty strings if `split('-')[0]` returns undefined. While the `|| ''` handles undefined, it still adds empty strings to the array which are then filtered later. Consider using `.filter(Boolean)` after the map to remove empty strings.

```suggestion
    const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : [])
      .map((code) => code.split('-')[0]?.toLowerCase())
      .filter((code): code is string => Boolean(code));
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not needed because we don't even need to default to an empty string.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're absolutely right! The || '' fallback is unnecessary here because split('-')[0] will always return a string (either the language code part or the original string if there's no hyphen). The conditional will never be undefined, so the empty string default adds no value and just creates potential empty strings that need filtering later.

@bmanquen bmanquen force-pushed the bm/YPE-1031-add-suggested-languages branch from ca4ed5e to 25534e3 Compare February 3, 2026 01:50
@bmanquen bmanquen force-pushed the bm/YPE-1031-add-suggested-languages branch from 25534e3 to aa21334 Compare February 3, 2026 01:56
@bmanquen
Copy link
Collaborator Author

bmanquen commented Feb 3, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +266 to +268
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
(code) => code.split('-')[0]?.toLowerCase(),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

split('-')[0] always returns a string, so the optional chaining ?. is unnecessary here. Remove it for cleaner code.

Suggested change
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
(code) => code.split('-')[0]?.toLowerCase(),
);
const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
(code) => code.split('-')[0].toLowerCase(),
);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/components/bible-version-picker.tsx
Line: 266:268

Comment:
`split('-')[0]` always returns a string, so the optional chaining `?.` is unnecessary here. Remove it for cleaner code.

```suggestion
    const userLanguageCodes = (typeof navigator !== 'undefined' ? navigator.languages : []).map(
      (code) => code.split('-')[0].toLowerCase(),
    );
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Collaborator Author

@bmanquen bmanquen Feb 3, 2026

Choose a reason for hiding this comment

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

TypeScript cannot know that there will be an element in the 0th index of the split, it is string | undefined to TypeScript.

Copy link
Collaborator

@cameronapak cameronapak left a comment

Choose a reason for hiding this comment

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

praise: You've done some good work. This is very thorough, and I appreciate the effort you've put into this.

question: I've had a lot of comments on this, and it was mainly around design things. So, I wanted to know, can I hop in and make some design updates just so that I can feel confident it matches the YouVersion standards for design, and so that we don't have to worry about going back and forth a lot for this PR to be merged in?

Copy link
Collaborator

Choose a reason for hiding this comment

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

544476146-f9f6319a-8bbe-47ea-9701-a0ce12189f8c

Here's the original horizontal padding on the component for reference:

Image

Nitpick (blocking): Can you improve the padding or margins so that the content can be closer to the edge of the left and right of the component of the picker?

--yv-chart-4: var(--yv-orange-30);
--yv-chart-5: var(--yv-magenta-30);
--yv-radius: 2rem;
--yv-radius: 1rem;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick/question (blocking): This change will affect every component that relies on this CSS variable. Almost every shadcn-ui component relies on this in some way, shape or form. How can you do what you wanted to do without changing this CSS variable?

data-slot="item-title"
className={cn(
'yv:flex yv:w-fit yv:items-center yv:gap-2 yv:text-sm yv:leading-snug yv:font-medium',
'yv:flex yv:w-fit yv:items-center yv:gap-2 yv:text-base yv:leading-snug yv:font-medium',
Copy link
Collaborator

Choose a reason for hiding this comment

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

quibble: Any change to our base shadcn-ui components will affect any other component that uses this component. Because of that I have to be protective in making sure that things like this don't accidentally mess up another component.

Can you do what you wanted to do without changing this component?

P.S. I will say that I am sorry if this comes across as very nitpicky. But probably one of the number one things that I'm protective of is the look and feel of the components. You're doing a great job, and I wanted to just point this stuff out just because it's important to me.

data-slot="item-description"
className={cn(
'yv:text-muted-foreground yv:line-clamp-2 yv:text-sm yv:leading-normal yv:font-normal yv:text-balance',
'yv:text-muted-foreground yv:line-clamp-2 yv:text-xs yv:leading-normal yv:font-normal yv:text-balance',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment on lines +208 to +212
/**
* I really dislike this api and want to make it like useLanguages above where you
* pass in an object. This would give us better typesafety and autocomplete through
* TypeScript which would be less error prone.
**/
Copy link
Collaborator

Choose a reason for hiding this comment

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

todo: please remove this comment. I agree with you on the sentiment. Perhaps you can make a ticket to see how we can update this hook?

Comment on lines -348 to +397
className="yv:bg-card yv:border yv:border-transparent yv:hover:bg-card yv:hover:border-border"
className="yv:bg-card yv:border yv:border-transparent yv:hover:bg-card yv:hover:border-border yv:max-w-42"
Copy link
Collaborator

Choose a reason for hiding this comment

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

question: What is the intent of this addition of a max width? What issues were you experiencing?

Comment on lines -354 to +404
<span className="yv:text-sm yv:font-medium yv:line-clamp-1">
{languages.find((language) => language.id === selectedLanguageId)?.englishName}
<span className="yv:text-sm yv:font-medium yv:truncate">
{selectedLanguage?.display_names?.en || selectedLanguage?.language}
Copy link
Collaborator

Choose a reason for hiding this comment

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

thing I learned: First time seeing the truncate tailwind class. Good find!

Comment on lines -377 to +427
className={`yv:min-h-0 yv:max-h-[66svh] yv:flex yv:flex-col yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${
className={`yv:h-full yv:flex yv:flex-col yv:overflow-hidden yv:transition-all yv:duration-300 yv:rounded-2xl yv:origin-center ${
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: Hey man, I'm starting to get concerned as I see lots of these CSS classes or Tailwind classes that I've added be changed.

It makes sense that you would want to and need to change things so that you could get your ticket done. However, I feel there are design changes and inconsistencies that have happened as a result.

I wanted to offer, can I hop in and make design changes to this so that I can feel peace that it meets the YouVersion design standard and quality?

Copy link
Collaborator

@cameronapak cameronapak left a comment

Choose a reason for hiding this comment

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

Cam's AI PR Review

2 items identified that weren't already covered by existing comments.


🤖 Generated by Cam's AI PR Review skill

.map((code) => uniqueLanguages.find((language) => language.id === code))
.filter((language) => language !== undefined);

const filteredCountryLanguages: Language[] =
Copy link
Collaborator

Choose a reason for hiding this comment

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

issue (blocking): Type mismatch - filteredCountryLanguages is typed as Language[] but uniqueLanguages is Pick<Language, 'id' | 'display_names'>[] per context type definition (line 144). The sort below accesses speaking_population which TypeScript thinks doesn't exist on these objects.

At runtime this may work if useLanguages returns full objects, but the type annotation is misleading. Consider:

  1. Adding speaking_population to the Pick type in context
  2. Or fixing the explicit type annotation here to match actual data shape

🤖 Cam's AI PR Review

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.

4 participants