Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ storybook-static

# Yarn (legacy)
.pnp.*
.pnpm-store
.yarn/*
!.yarn/patches
!.yarn/plugins
Expand Down
19 changes: 15 additions & 4 deletions packages/ui/src/components/YouVersionAuthButton.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useMemo } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import i18n from '@/i18n';
import { LoaderIcon } from './icons/loader';
import { type AuthenticationScopes } from '@youversion/platform-core';
import { useYVAuth, useTheme } from '@youversion/platform-react-hooks';
Expand Down Expand Up @@ -126,6 +128,7 @@ export const YouVersionAuthButton = React.forwardRef<HTMLButtonElement, YouVersi
const { signIn, signOut, auth } = useYVAuth();
const providerTheme = useTheme();
const theme = background || providerTheme;
const { t } = useTranslation(undefined, { i18n });

const handleClick = async (e: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
e.preventDefault();
Expand Down Expand Up @@ -158,19 +161,27 @@ export const YouVersionAuthButton = React.forwardRef<HTMLButtonElement, YouVersi
const isSignOut = mode === 'signOut' || (mode === 'auto' && auth.isAuthenticated);

if (size === 'short') {
return isSignOut ? 'Sign out' : 'Sign in';
return isSignOut ? t('signOut') : t('signIn');
}

return isSignOut ? (
<div className="yv:font-normal">
Sign out of <span className="yv:font-bold">YouVersion</span>
<Trans
Comment thread
camrun91 marked this conversation as resolved.
i18nKey="signOutOfYouVersion"
i18n={i18n}
components={{ bold: <span className="yv:font-bold" /> }}
/>
</div>
) : (
<div className="yv:font-normal">
Sign in with <span className="yv:font-bold">YouVersion</span>
<Trans
Comment thread
camrun91 marked this conversation as resolved.
i18nKey="signInWithYouVersion"
i18n={i18n}
components={{ bold: <span className="yv:font-bold" /> }}
/>
</div>
);
}, [mode, auth.isAuthenticated, size, text]);
}, [mode, auth.isAuthenticated, size, text, t]);

const loadingSpinner = (
<LoaderIcon className="yv:z-20 yv:absolute yv:left-1/2 yv:top-1/2 yv:animate-spin yv:-translate-x-1/2 yv:-translate-y-1/2 yv:fill-primary-foreground yv:text-primary" />
Expand Down
144 changes: 74 additions & 70 deletions packages/ui/src/components/bible-app-logo-lockup.tsx

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions packages/ui/src/components/bible-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { usePassage, useVersion, useTheme } from '@youversion/platform-react-hooks';
import { DEFAULT_LICENSE_FREE_BIBLE_VERSION } from '@youversion/platform-core';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import { BibleTextView, type FootnoteData } from './verse';
import { BibleAppLogoLockup } from './bible-app-logo-lockup';
import { BibleVersionPicker, type BibleVersionPickerPressData } from './bible-version-picker';
Expand Down Expand Up @@ -41,10 +43,11 @@ export type BibleCardProps = {
};

function BibleCardHeaderError(): React.ReactNode {
const { t } = useTranslation(undefined, { i18n });
return (
<div className="yv:flex yv:flex-col yv:gap-2" role="alert" aria-live="polite">
<h2 className="yv:font-bold yv:tracking-widest yv:text-xs yv:uppercase yv:text-foreground">
Error
{t('errorHeading')}
</h2>
</div>
);
Expand Down Expand Up @@ -75,14 +78,15 @@ function BibleCardVersionPicker({
theme: 'light' | 'dark';
onVersionPickerPress?: (data: BibleVersionPickerPressData) => void;
}): React.ReactNode {
const { t } = useTranslation(undefined, { i18n });
return (
<BibleVersionPicker.Root
onVersionChange={onVersionChange}
versionId={versionId}
background={theme}
onVersionPickerPress={onVersionPickerPress}
>
<BibleVersionPicker.Trigger aria-label="Change Bible version">
<BibleVersionPicker.Trigger aria-label={t('changeBibleVersionAriaLabel')}>
{({ version, loading }) => (
<Button
variant="secondary"
Expand All @@ -96,7 +100,7 @@ function BibleCardVersionPicker({
aria-hidden="true"
/>
) : (
version?.localized_abbreviation || 'Select version'
version?.localized_abbreviation || t('selectVersion')
)}
</Button>
)}
Expand Down
17 changes: 11 additions & 6 deletions packages/ui/src/components/bible-chapter-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import {
cloneElement,
createContext,
Expand Down Expand Up @@ -89,6 +91,7 @@ function Root({
onChapterPickerPress,
children,
}: RootProps) {
const { t } = useTranslation(undefined, { i18n });
const [book, setBook] = useControllableState({
prop: controlledBook,
defaultProp: defaultBook,
Expand Down Expand Up @@ -203,7 +206,7 @@ function Root({
{children}

{/* data-yv-sdk for styles is needed because the popover gets rendered outside of the providers scope **/}
<PopoverContent sideOffset={16} heading="Books" theme={theme} side="top">
<PopoverContent sideOffset={16} heading={t('booksHeading')} theme={theme} side="top">
<Content onRequestClose={() => setIsPopoverOpen(false)} />
</PopoverContent>
</BibleChapterPickerContext.Provider>
Expand All @@ -226,6 +229,7 @@ export type TriggerProps = Omit<React.ComponentProps<typeof PopoverTrigger>, 'ch
};

function Trigger({ asChild = true, children, ...props }: TriggerProps) {
const { t } = useTranslation(undefined, { i18n });
const { book, chapter, background, versionId, scrollToCurrentBook, onChapterPickerPress } =
useBibleChapterPickerContext();
const { books, loading } = useBooks(versionId);
Expand All @@ -239,8 +243,8 @@ function Trigger({ asChild = true, children, ...props }: TriggerProps) {
chapterLabel = currentBook.intro.title;
}
const buttonText = loading
? 'Loading...'
: `${currentBook?.title || 'Select a chapter'}${chapterLabel ? ` ${chapterLabel}` : ''}`;
? t('loadingEllipsis')
: `${currentBook?.title || t('selectChapter')}${chapterLabel ? ` ${chapterLabel}` : ''}`;

const content =
typeof children === 'function'
Expand Down Expand Up @@ -291,6 +295,7 @@ function Trigger({ asChild = true, children, ...props }: TriggerProps) {
}

function Content({ onRequestClose, onSelect }: BibleChapterPickerContentProps) {
const { t } = useTranslation(undefined, { i18n });
const {
book,
defaultBook,
Expand Down Expand Up @@ -372,15 +377,15 @@ function Content({ onRequestClose, onSelect }: BibleChapterPickerContentProps) {
</div>
) : (
<div className="yv:w-full yv:flex yv:items-center yv:justify-center yv:py-4 yv:text-muted-foreground yv:text-sm">
No chapters available
{t('noChaptersAvailable')}
</div>
)}
</AccordionContent>
</AccordionItem>
))
) : (
<div className="yv:w-full yv:h-full yv:flex yv:items-center yv:justify-center yv:py-4 yv:text-center yv:text-balance yv:text-muted-foreground yv:text-sm">
We're sorry, there are no Bible results for this search.
{t('noBibleSearchResults')}
</div>
)}
</Accordion>
Expand All @@ -390,7 +395,7 @@ function Content({ onRequestClose, onSelect }: BibleChapterPickerContentProps) {
<InputGroupInput
tabIndex={1}
type="text"
placeholder="Search"
placeholder={t('searchPlaceholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
Expand Down
50 changes: 29 additions & 21 deletions packages/ui/src/components/bible-reader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use client';

import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
import { useControllableState } from '@radix-ui/react-use-controllable-state';
import {
useBooks,
Expand Down Expand Up @@ -310,6 +312,7 @@ function Root({
}

function Content() {
const { t } = useTranslation(undefined, { i18n });
const {
background,
book,
Expand Down Expand Up @@ -361,10 +364,8 @@ function Content() {
</h1>

{chapterUnavailable ? (
// This copy was taken from bible.com (e.g. https://www.bible.com/bible/4253/ACT.INTRO1.AFV)
<p className="yv:text-center yv:text-balance yv:text-muted-foreground">
This chapter is not available in this version. Please choose a different chapter or
version.
{t('chapterUnavailable')}
</p>
) : (
<BibleTextView
Expand Down Expand Up @@ -394,7 +395,7 @@ function Content() {
target="_blank"
rel="noopener noreferrer"
>
<InfoIcon className="yv:size-4" /> Learn More
<InfoIcon className="yv:size-4" /> {t('learnMore')}
</a>
) : null}
</footer>
Expand All @@ -404,6 +405,7 @@ function Content() {
}

function UserMenu() {
const { t } = useTranslation(undefined, { i18n });
const { auth, signIn, signOut, userInfo } = useYVAuth();
const yvContext = useContext(YouVersionContext);

Expand All @@ -414,7 +416,7 @@ function UserMenu() {
<Button size="icon" variant="outline">
<img
src={userInfo.getAvatarUrl(32, 32)?.toString()}
alt={userInfo.name || 'User avatar'}
alt={userInfo.name || t('userAvatarAlt')}
className="yv:size-full yv:rounded-full yv:object-cover"
/>
</Button>
Expand All @@ -434,15 +436,15 @@ function UserMenu() {
<PopoverClose asChild>
{auth.isAuthenticated ? (
<Button variant="secondary" className="yv:card yv:text-foreground" onClick={signOut}>
Sign Out
{t('signOut')}
</Button>
) : (
<Button
variant="secondary"
className="yv:card yv:text-foreground"
onClick={() => void signIn({ scopes: ['profile'] })}
>
Sign In
{t('signIn')}
</Button>
)}
</PopoverClose>
Expand All @@ -459,6 +461,7 @@ export function BibleThemeSettingsContent({
onFontIncreased,
onFontDecreased,
}: BibleThemeSettingsContentProps): ReactElement {
const { t } = useTranslation(undefined, { i18n });
return (
<div data-yv-sdk data-yv-theme={theme} className="yv:flex yv:flex-col yv:gap-4 yv:p-4">
<div className="yv:grid yv:grid-cols-2">
Expand Down Expand Up @@ -506,9 +509,9 @@ export function BibleThemeSettingsContent({
: '',
)}
>
Font
{t('fontLabel')}
</span>
<span className="yv:sm:text-xl yv:text-base">Inter</span>
<span className="yv:sm:text-xl yv:text-base">{t('interFontName')}</span>
</div>
</Button>
<Button
Expand All @@ -530,9 +533,11 @@ export function BibleThemeSettingsContent({
: '',
)}
>
Font
{t('fontLabel')}
</span>
<span className="yv:sm:text-xl yv:text-base yv:font-serif">
{t('sourceSerifFontName')}
</span>
<span className="yv:sm:text-xl yv:text-base yv:font-serif">Source Serif</span>
</div>
</Button>
</div>
Expand All @@ -546,6 +551,7 @@ export type BibleReaderToolbarProps = {
};

function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolbarProps) {
const { t } = useTranslation(undefined, { i18n });
const {
book,
chapter,
Expand Down Expand Up @@ -658,7 +664,7 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
size="icon"
variant="ghost"
disabled={!canNavigatePrevious}
aria-label="Previous chapter"
aria-label={t('previousChapterAriaLabel')}
onClick={(e) => {
e.stopPropagation();
if (prevResult) {
Expand All @@ -675,14 +681,14 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
variant="secondary"
className="yv:px-0 yv:font-bold yv:text-foreground yv:min-w-[5ch]"
disabled={loading}
aria-label="Change Bible book and chapter"
aria-label={t('changeBibleBookAndChapterAriaLabel')}
>
{loading ? (
<LoaderIcon className="yv:size-4 yv:animate-spin yv:text-muted-foreground" />
) : (
<>
<span className="yv:min-w-[3ch] yv:truncate">
{currentBook?.title || 'Select'}
{currentBook?.title || t('select')}
</span>
<span className="yv:tabular-nums yv:min-w-[1ch] yv:truncate">
{chapterLabel || ''}
Expand All @@ -703,7 +709,7 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
size="icon"
variant="ghost"
disabled={!canNavigateNext}
aria-label="Next chapter"
aria-label={t('nextChapterAriaLabel')}
>
<ChevronRightIcon className="yv:transition-transform yv:duration-100 yv:group-active:translate-y-px" />
</Button>
Expand All @@ -718,22 +724,24 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
background={background}
onVersionPickerPress={onVersionPickerPress}
>
<BibleVersionPicker.Trigger aria-label="Change Bible version">
<BibleVersionPicker.Trigger aria-label={t('changeBibleVersionAriaLabel')}>
{({ version, loading }) => (
<Button
size="lg"
variant="secondary"
className="yv:min-w-[calc(0.25rem*4*2+3ch)] yv:px-4 yv:font-bold yv:text-foreground"
disabled={loading}
aria-label={loading ? 'Loading Bible version' : 'Change Bible version'}
aria-label={
loading ? t('loadingBibleVersionAriaLabel') : t('changeBibleVersionAriaLabel')
}
>
{/* This div exists merely as a wrapper to minimize width layout shifting */}
<div className="yv:min-w-[3ch] yv:flex yv:justify-center">
{loading ? (
<LoaderIcon className="yv:size-4 yv:animate-spin yv:text-muted-foreground" />
) : (
<span className="yv:truncate">
{version?.localized_abbreviation || 'Select version'}
{version?.localized_abbreviation || t('selectVersion')}
</span>
)}
</div>
Expand All @@ -747,20 +755,20 @@ function Toolbar({ border = 'top', onOpenBibleThemeSettings }: BibleReaderToolba
<Button
size="sm"
variant="secondary"
aria-label="Settings"
aria-label={t('settingsAriaLabel')}
onClick={() => onOpenBibleThemeSettings(buildBibleThemeSettingsSnapshot())}
>
<GearIcon className="yv:text-foreground" />
</Button>
) : (
<Popover>
<PopoverTrigger asChild aria-label="Settings">
<PopoverTrigger asChild aria-label={t('settingsAriaLabel')}>
<Button size="sm" variant="secondary">
<GearIcon className="yv:text-foreground" />
</Button>
</PopoverTrigger>

<PopoverContent sideOffset={16} heading="Reader Settings" theme={background}>
<PopoverContent sideOffset={16} heading={t('readerSettingsHeading')} theme={background}>
<BibleThemeSettingsContent
theme={background}
fontSize={currentFontSize}
Expand Down
Loading
Loading