Skip to content

feat(book-chapter-control): add hideVerse prop to omit verse from trigger label#2239

Open
MattGyverLee wants to merge 5 commits into
paranext:mainfrom
MattGyverLee:feat/book-chapter-control-hide-verse
Open

feat(book-chapter-control): add hideVerse prop to omit verse from trigger label#2239
MattGyverLee wants to merge 5 commits into
paranext:mainfrom
MattGyverLee:feat/book-chapter-control-hide-verse

Conversation

@MattGyverLee
Copy link
Copy Markdown

@MattGyverLee MattGyverLee commented May 1, 2026

Summary

  • Add an optional hideVerse?: boolean prop to BookChapterControl. When true, the trigger button's label displays <book> <chapter> only (e.g. Psalms 23), omitting the verse part.
  • Verse omission is locale-safe: formatScrRefRange(refWithoutVerse, refWithoutVerse, ...) with verseNum: -1 lets the util drop the verse part. No regex on formatted output.
  • Surface the chapter/verse and book/chapter separators through localizedStrings, so locales can render scripture references with their own typography conventions.
  • Adds a HideVerse Storybook story.
  • Locale data: en.json and fr.json populated; French refs render as Jean 3.16 (period separator), and numeric-prefix French book names ("1 Samuel", "1 Jean", …) now use a non-breaking space between the digit and the noun so refs don't break across lines.

Motivation

Chapter-level workflows have no concept of a starting verse. Examples:

  • Whole-chapter / chapter-range exports (e.g. exporting 1 John 3 to FLEx for linguistic analysis).
  • Scope selectors that operate on chapters.

Today these consumers must show 1 John 3:1, which suggests verse-level granularity that doesn't exist in the workflow. There is no consumer-side way to fix this — formatScrRef always emits book chapter:verse, and the trigger renders that result directly with no override hook.

The localization changes address a related gap: even where the active locale would conventionally render scripture refs differently (French 3.16 vs. English 3:16), the trigger label and the popover's internal references were hardcoded to use : between chapter and verse.

Behavior

Configuration Trigger label example
hideVerse unset, English UI 1 John 3:1 (current behavior, unchanged)
hideVerse={true}, English UI 1 John 3
hideVerse unset, French UI 1 Jean 3.1
hideVerse={true}, French UI 1 Jean 3

Localization

Two new optional localized-string keys, exported via BOOK_CHAPTER_CONTROL_STRING_KEYS:

Key Util default English (en.json) French (fr.json)
%scripture_book_chapter_separator% " " " " " "
%scripture_chapter_verse_separator% ":" ":" "."

useLocalizedStrings returns the literal placeholder (e.g. "%scripture_chapter_verse_separator%") for any locale that doesn't define a value (see src/renderer/hooks/papi-hooks/use-localized-strings-hook.ts). The component drops any value matching that placeholder shape, so formatScrRef / formatScrRefRange's built-in defaults (" " / ":") apply. Locales that haven't been touched (Spanish, Khmer, Chinese, …) are unchanged.

Existing consumers — find.web-view.tsx, scope selectors, etc. — already pass BOOK_CHAPTER_CONTROL_STRING_KEYS to useLocalizedStrings, so they pick up the new keys automatically. No consumer-side change required.

Also added French translations for the four scripture-section labels surfaced by BookChapterControl's popover groupings:

Key English French
%scripture_section_ot_long% Old Testament Ancien Testament
%scripture_section_nt_long% New Testament Nouveau Testament
%scripture_section_dc_long% Deuterocanon Deutérocanon
%scripture_section_extra_long% Extra material Suppléments
%scripture_section_ot_short% OT AT
%scripture_section_nt_short% NT NT
%scripture_section_dc_short% DC DC
%scripture_section_extra_short% Extra Suppl.

French typography fix

In French typography, numeric-prefix book names like "1 Samuel" or "1 Jean" should not break across the digit and the following word. Updated 31 entries in fr.json to use a non-breaking space (U+00A0) between the digit and the book name. Affects: 1SA, 2SA, 1KI, 2KI, 1CH, 2CH, 1CO, 2CO, 1TH, 2TH, 1TI, 2TI, 1PE, 2PE, 1JN, 2JN, 3JN, 1MA, 2MA, 3MA, 4MA, 1ES, 2ES, 3ES, 5EZ, 6EZ, 2BA, 1MQ, 2MQ, 3MQ, 4BA.

Test plan

  • npm run typecheck in lib/platform-bible-react passes.
  • npm test -- --run src/components/advanced/book-chapter-control passes.
  • Storybook: open the HideVerse story; confirm the trigger renders <book> <chapter> and that popover/selection still works and handleSubmit still receives a full SerializedVerseRef.
  • Switch interface language to French and verify the trigger label renders <book> <chapter>.<verse> (period separator); confirm 1 Jean does not break across lines when the trigger is narrow.
  • French popover shows Ancien Testament, Nouveau Testament, Deutérocanon, Suppléments as section headings (no untranslated %...% placeholders).
  • Manual cross-check: existing stories without hideVerse continue to render <book> <chapter>:<verse> in English.

Notes

  • All four files in this PR are additive: existing callers and locales keep their current behavior.
  • The hardcoded 'English' book name in the RecentSearches renderItem is intentionally left as-is — book-name localization for that path is a wider gap that should be addressed separately (every consumer of BookChapterControl currently has to assemble its own localizedBookNames map; a useLocalizedBookNames hook would let us drop that boilerplate everywhere).

This change is Reviewable

…gger label

The BookChapterControl trigger always displays "<book> <chapter>:<verse>" via
formatScrRef, with no way to suppress the verse. Chapter-level workflows
(e.g. whole-chapter exports, chapter scopes) where the verse is conceptually
fixed at 1 end up showing a misleading ":1" suffix.

Add an optional hideVerse boolean prop. When true, the formatted display
value strips the trailing ":<digits>" with a regex (the underlying
SerializedVerseRef and submitted handlers are unaffected). The popover-driven
selection behavior is unchanged.

Includes a Storybook story (HideVerse) demonstrating the new prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se=true

When `hideVerse` is true, the previous-verse and next-verse chevron buttons
in the popover header become misleading (they would let users mutate a
verse number that is intentionally hidden from the trigger label and not
meaningful in the consumer's workflow). Filter them out alongside the
trigger label change so chapter-only consumers get a coherent UI:
only previous-chapter / next-chapter chevrons remain.

`useQuickNavButtons` gains an optional `hideVerse` parameter (defaults to
false) so the existing 4-button behavior is unchanged when the prop is
not set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@tjcouch-sil tjcouch-sil left a comment

Choose a reason for hiding this comment

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

Very nice! Thanks for contributing :) I have one optional thing for you to consider; I'd love to hear your thoughts!

@tjcouch-sil reviewed 1 file and made 2 comments.
Reviewable status: 1 of 5 files reviewed, 1 unresolved discussion (waiting on MattGyverLee).


lib/platform-bible-react/src/components/advanced/book-chapter-control/book-chapter-control.component.tsx line 293 at r1 (raw file):

      // produced with the default chapter/verse separator. The book name itself never
      // ends with ":<digits>", so this is unambiguous.
      return formatScrRef(scrRef, bookName).replace(/:\d+$/, '');

Did you by any chance see formatScrRefRange in platform-bible-utils? It's rather new and wasn't created by one of our full-time developers, so it's a bit rough around the edges - its TSDoc doesn't mention it, but it will not display endScrRef if you provide the same value (equality checked via compareScrRefs(...) === 0) for both start and end. Additionally, it doesn't show the verse if it's -1. So you could make a new serialized verse ref whose verseNum is -1, then pass it in as both start and end in formatScrRefRange, and I think that would get you what you want. But it's much dirtier than a simple little regex.

But using an existing function in our API would presumably be a more solid long-term way to accomplish this since we'll presumably support other localizations of scripture references through that utility method one day, whereas this regex could get broken without our realizing it very easily. This is a little bit of a messy area in the API; sorry. Someone made it quickly relative to the complexity of formatting scripture references.

@MattGyverLee
Copy link
Copy Markdown
Author

MattGyverLee commented May 4, 2026

My main goal was to introduce a flag that had the desired effect of removing the verse and the verse nav chevrons. I figured other plugins might also want to select book and chapter without the precision of verses. For now, I'm overriding it in my extension code while I wait for this to land.

Yes, something more robust than a regex makes sense. I've already realized that this would break in interface languages that don't use 5:1 for verse refs, like French that uses 5.1. It may also break with verse parts like 1a. If the solution you propose is (or will be) locale and script-safe, then we should use it!

So are you saying the hideVerse flag should set the scrRef to, for example, 5:-1 before running formatScrRef?

Before:
image

After:
image

Copy link
Copy Markdown
Member

@tjcouch-sil tjcouch-sil left a comment

Choose a reason for hiding this comment

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

@tjcouch-sil made 1 comment.
Reviewable status: 1 of 5 files reviewed, 1 unresolved discussion (waiting on MattGyverLee).


lib/platform-bible-react/src/components/advanced/book-chapter-control/book-chapter-control.component.tsx line 293 at r1 (raw file):

Previously, tjcouch-sil (TJ Couch) wrote…

Did you by any chance see formatScrRefRange in platform-bible-utils? It's rather new and wasn't created by one of our full-time developers, so it's a bit rough around the edges - its TSDoc doesn't mention it, but it will not display endScrRef if you provide the same value (equality checked via compareScrRefs(...) === 0) for both start and end. Additionally, it doesn't show the verse if it's -1. So you could make a new serialized verse ref whose verseNum is -1, then pass it in as both start and end in formatScrRefRange, and I think that would get you what you want. But it's much dirtier than a simple little regex.

But using an existing function in our API would presumably be a more solid long-term way to accomplish this since we'll presumably support other localizations of scripture references through that utility method one day, whereas this regex could get broken without our realizing it very easily. This is a little bit of a messy area in the API; sorry. Someone made it quickly relative to the complexity of formatting scripture references.

Yes, I believe you understand correctly my proposal. Sorry it's not documented carefully yet.

@MattGyverLee
Copy link
Copy Markdown
Author

MattGyverLee commented May 4, 2026

ok, done as suggested. I'm not really at a place to try building platform.bible from source to test it. Once this lands, I'll want to back off my workaround in the extension.

MattGyverLee and others added 2 commits May 4, 2026 16:59
… uses period)

Read %scripture_book_chapter_separator% and %scripture_chapter_verse_separator% from localizedStrings and pass them to formatScrRef / formatScrRefRange. When a locale does not define a key, useLocalizedStrings echoes the placeholder back; values matching '%...%' are dropped so the util defaults (' ' / ':') apply.

Locale data:
- Add separator entries to en.json (':' / ' ') and fr.json ('.' / ' ').
- Add French translations %scripture_section_ot_long% / %scripture_section_nt_long%.
- Replace the ASCII space between the digit and the book name in 31 numeric-prefix fr.json entries (1 Samuel, 1 Jean, etc.) with U+00A0 so refs do not break across lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…and Extra section labels

Translate %scripture_section_dc_long%, %scripture_section_dc_short%, %scripture_section_extra_long%, and %scripture_section_extra_short% in fr.json. With the OT/NT additions from the previous commit, all four section labels surfaced by BookChapterControl now have French equivalents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MattGyverLee
Copy link
Copy Markdown
Author

MattGyverLee commented May 4, 2026

My plugin is already localized. The formatScrRef change as it was resulted in broken book names because localizedBookNames wasn't fully wired up.

Now it is.
image

PR updated. Only a small feature creep. :)

Ready for a new 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.

2 participants