Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c004ba2
feat(i18n): 홈과 계정 화면 한국어 번역 보완
devy1540 Aug 6, 2026
f78ea63
feat(i18n): 공통 상태와 권한 문구 번역
devy1540 Aug 6, 2026
b037127
feat(i18n): 라이브러리 생성 문구 번역
devy1540 Aug 6, 2026
8e15349
feat(i18n): 라이브러리 관리 문구 번역
devy1540 Aug 6, 2026
7f35a12
feat(i18n): 책과 메타데이터 문구 번역
devy1540 Aug 6, 2026
1b11698
feat(i18n): 스마트 목록 필터 문구 번역
devy1540 Aug 6, 2026
3b78402
feat(i18n): 스마트 목록 화면 문구 번역
devy1540 Aug 6, 2026
8b395e7
feat(i18n): 북클럽 화면 문구 번역
devy1540 Aug 6, 2026
dd42b1f
feat(i18n): 개인 설정 화면 문구 번역
devy1540 Aug 6, 2026
e125a42
feat(i18n): 서버 작업과 로그 문구 번역
devy1540 Aug 6, 2026
01efa3d
feat(i18n): 사용자 관리 문구 번역
devy1540 Aug 6, 2026
148df7f
feat(i18n): 서버 연동 설정 문구 번역
devy1540 Aug 6, 2026
0b9b128
feat(i18n): 모바일 서버와 라이브러리 문구 번역
devy1540 Aug 6, 2026
69023ce
feat(i18n): 모바일 리더 문구 번역
devy1540 Aug 6, 2026
f9bc60a
feat(i18n): 모바일 설정과 정렬 문구 번역
devy1540 Aug 6, 2026
a2507a3
feat(i18n): 테이블 열 구성 문구 국제화
devy1540 Aug 6, 2026
1f64021
feat(i18n): 테이블 헤더 문구 국제화
devy1540 Aug 6, 2026
c1052a3
feat(i18n): 공통 화면 문구 국제화
devy1540 Aug 6, 2026
af201b8
feat(i18n): 메타데이터 관리 문구 국제화
devy1540 Aug 6, 2026
b3891a3
feat(i18n): 설정과 서버 요약 문구 국제화
devy1540 Aug 6, 2026
619ce0c
feat(i18n): 탐색과 검색 문구 국제화
devy1540 Aug 6, 2026
7f418f8
feat(i18n): 사용자 목록과 로그인 기록 국제화
devy1540 Aug 6, 2026
b29a6ae
feat(i18n): 웹 리더 문구 국제화
devy1540 Aug 6, 2026
d3e98c1
feat(i18n): 책과 시리즈 상세 문구 국제화
devy1540 Aug 6, 2026
4924911
feat(i18n): 공통 컨트롤 문구 국제화
devy1540 Aug 6, 2026
476c25d
feat(i18n): 필터 화면 문구 국제화
devy1540 Aug 6, 2026
28f300f
feat(i18n): 라이브러리 관리 문구 국제화
devy1540 Aug 6, 2026
2e95fc2
feat(i18n): 설정 부속 화면 문구 국제화
devy1540 Aug 6, 2026
94dd290
feat(i18n): 북클럽 관리 문구 국제화
devy1540 Aug 6, 2026
9486b4f
feat(i18n): 웹 잔여 화면 문구 국제화
devy1540 Aug 6, 2026
30064de
feat(i18n): 웹 공통 컨트롤 문구 국제화
devy1540 Aug 6, 2026
1a22a6a
feat(i18n): 책과 리더 상태 문구 국제화
devy1540 Aug 6, 2026
297661e
feat(i18n): 생성과 목록 관리 문구 국제화
devy1540 Aug 6, 2026
6642d0e
feat(i18n): 웹 설정과 관리 알림 국제화
devy1540 Aug 6, 2026
5177979
feat(i18n): 모바일 오류와 인증 문구 국제화
devy1540 Aug 6, 2026
9959284
feat(i18n): 모바일 OPDS 문구 국제화
devy1540 Aug 6, 2026
1be2487
feat(i18n): 모바일 책과 리더 문구 국제화
devy1540 Aug 6, 2026
02aca54
feat(i18n): 모바일 북클럽 문구 국제화
devy1540 Aug 6, 2026
201b46c
feat(i18n): 모바일 잔여 화면 문구 국제화
devy1540 Aug 6, 2026
566ff21
feat(i18n): 웹 공용 메뉴와 리더 옵션 국제화
devy1540 Aug 6, 2026
0707c5b
feat(i18n): 웹 검증 오류와 처리 알림 국제화
devy1540 Aug 6, 2026
c7b82cf
feat(i18n): 모바일 OPDS와 접근성 문구 국제화
devy1540 Aug 6, 2026
f02ab44
feat(i18n): 모바일 가져오기와 동기화 알림 국제화
devy1540 Aug 6, 2026
57c81df
feat(i18n): 스마트 목록 필터 검증 국제화
devy1540 Aug 6, 2026
6710d94
feat(i18n): 웹 빈 상태와 보조 문구 국제화
devy1540 Aug 6, 2026
1b5efe4
feat(i18n): 웹 업로드와 작업 알림 국제화
devy1540 Aug 6, 2026
14e2258
feat(i18n): 모바일 OPDS와 토론 보조 문구 국제화
devy1540 Aug 6, 2026
58f684e
fix(i18n): 잘못된 번역 키 연결 수정
devy1540 Aug 6, 2026
f8cb662
feat(i18n): 공용 보조 문구와 개수 표현 국제화
devy1540 Aug 6, 2026
2e196c7
feat(i18n): 접근성 문구와 기본 제목 국제화
devy1540 Aug 6, 2026
7f72086
style(i18n): 번역 훅 import와 의존성 정리
devy1540 Aug 6, 2026
8ed2cdb
test(i18n): 국제화된 리더와 API 키 문구 검증
devy1540 Aug 6, 2026
71ade5a
fix(i18n): keep locale available to error fallback
Aug 8, 2026
5bf683f
Merge branch 'nightly' of https://github.com/stumpapp/stump into feat…
Aug 8, 2026
b9f7ebd
refactor(i18n): group locale keys by component
Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions apps/expo/app/(tabs)/library/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { usePreferencesStore } from '~/stores'
import { useSelectionStore } from '~/stores/selection'

export default function Screen() {
const { t } = useTranslate()
// Note: The id is a workaround for https://github.com/drizzle-team/drizzle-orm/issues/2660
const { id, increment, sortConfig, sourceFilter } = useDownloadsState(
useShallow((state) => ({
Expand Down Expand Up @@ -89,11 +90,11 @@ export default function Screen() {
// We create a sectioned list by grouping by series, then flatten it so that we have something like:
// ["Series 1", item1, item2, "Series 2", item3, item4]
// See https://shopify.github.io/flash-list/docs/guides/section-list/
const grouped = groupBy(data, (item) => item.series_refs?.name || 'Unknown')
const grouped = groupBy(data, (item) => item.series_refs?.name || t('common.unknown'))
return Object.entries(grouped).flatMap(([seriesName, items]) => {
return [seriesName, ...items]
})
}, [sortConfig, data])
}, [sortConfig, data, t])

const renderItem = useCallback(({ item }: { item: (typeof data)[0] | string }) => {
if (typeof item === 'string') {
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/(tabs)/settings/usage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function Screen() {
{savedServers.length > 0 && (
<Card
label={t('common.servers')}
listEmptyStyle={{ icon: Server, message: 'No servers added' }}
listEmptyStyle={{ icon: Server, message: t('emptyState.noServersAdded') }}
>
{savedServers.map((server) => (
<Pressable
Expand Down
12 changes: 6 additions & 6 deletions apps/expo/app/offline/[fileId]/read.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
toAbsolutePath,
unpackedBookDirectory,
} from '~/lib/filesystem'
import { useLocalAnnotationMutations, useLocalBookmarkMutations } from '~/lib/hooks'
import { useLocalAnnotationMutations, useLocalBookmarkMutations, useTranslate } from '~/lib/hooks'
import type { ReadiumLocator } from '~/modules/readium'
import { intoReadiumLocator } from '~/modules/readium'
import StumpStreamer from '~/modules/streamer'
Expand All @@ -45,6 +45,7 @@ type Params = {
// TODO: Follow https://github.com/dexie/Dexie.js/pull/2205

export default function Screen() {
const { t } = useTranslate()
useKeepAwake()

const { fileId } = useLocalSearchParams<Params>()
Expand Down Expand Up @@ -76,7 +77,7 @@ export default function Screen() {
)

if (!record && !!updatedAt) {
throw new Error('Downloaded file not found')
throw new Error(t('errors.downloadedFileNotFound'))
}

if (!record) {
Expand All @@ -102,6 +103,7 @@ type ReaderProps = {
}

function Reader({ record, bookmarks, annotations }: ReaderProps) {
const { t } = useTranslate()
const downloadedFile = useMemo(() => record.downloaded_files, [record])

const unsyncedProgress = useMemo(() => record.read_progress, [record])
Expand Down Expand Up @@ -144,12 +146,10 @@ function Reader({ record, bookmarks, annotations }: ReaderProps) {
})
console.error('Failed to initialize streamer:', error)
setStreamerError(
error instanceof Error
? error
: new Error('Failed to initialize streamer. Please reach out for support'),
error instanceof Error ? error : new Error(t('errors.streamerInitializationFailed')),
)
}
}, [book.id, downloadedFile.serverId, downloadedFile.uri])
}, [book.id, downloadedFile.serverId, downloadedFile.uri, t])

useEffect(
() => {
Expand Down
6 changes: 4 additions & 2 deletions apps/expo/app/opds-legacy/[id]/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { NativeTabs } from 'expo-router/unstable-native-tabs'

import { useOPDSLegacyFeedContext } from '~/context/opdsLegacy'
import { useColors } from '~/lib/constants'
import { useTranslate } from '~/lib/hooks'

// TODO(opds): Support favorites and add a tab for it

export default function TabLayout() {
const { t } = useTranslate()
const { sdk } = useSDK()
const { hasSearch } = useOPDSLegacyFeedContext()

Expand All @@ -26,15 +28,15 @@ export default function TabLayout() {
labelVisibilityMode="labeled"
>
<NativeTabs.Trigger name="feed">
<NativeTabs.Trigger.Label>Feed</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Label>{t('opds.feed')}</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon
sf={{ default: 'dot.radiowaves.up.forward', selected: 'dot.radiowaves.up.forward' }}
md="rss_feed"
/>
</NativeTabs.Trigger>
{hasSearch && (
<NativeTabs.Trigger name="search" role="search">
<NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Label>{t('opds.search')}</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="magnifyingglass" md="search" />
</NativeTabs.Trigger>
)}
Expand Down
7 changes: 4 additions & 3 deletions apps/expo/app/opds-legacy/[id]/(tabs)/feed/[url].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import { useLegacyOPDSEntrySize } from '~/components/opdsLegacy/useLegacyOPDSEnt
import RefreshControl from '~/components/RefreshControl'
import { FullScreenLoader } from '~/components/ui'
import { ON_END_REACHED_THRESHOLD } from '~/lib/constants'
import { useLegacyOPDSFeed } from '~/lib/hooks'
import { useLegacyOPDSFeed, useTranslate } from '~/lib/hooks'
import { useDynamicHeader } from '~/lib/hooks/useDynamicHeader'

export default function Screen() {
const { t } = useTranslate()
const { url: feedURL } = useLocalSearchParams<{ url: string }>()
const {
feed, // The current page feed
Expand All @@ -43,14 +44,14 @@ export default function Screen() {
}
}

if (showLoader) return <FullScreenLoader label="Loading..." />
if (showLoader) return <FullScreenLoader label={t('opds.loading')} />

if (isLoading) return null

if (!feed || !!error) return <MaybeErrorLegacyFeed error={error} onRetry={onRefetch} />

if (!entries.length) {
return <EmptyState title="Nothing to show" message="No entries were returned for this feed" />
return <EmptyState title={t('opds.nothingToShow')} message={t('opds.noEntries')} />
}

return (
Expand Down
4 changes: 3 additions & 1 deletion apps/expo/app/opds-legacy/[id]/(tabs)/feed/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Stack } from 'expo-router'
import { Platform } from 'react-native'

import { IS_IOS_26_PLUS } from '~/lib/constants'
import { useTranslate } from '~/lib/hooks'
import { usePreferencesStore } from '~/stores'

export default function Layout() {
const { t } = useTranslate()
const animationEnabled = usePreferencesStore((state) => !state.reduceAnimations)

return (
Expand All @@ -18,7 +20,7 @@ export default function Layout() {
name="index"
options={{
headerShown: true,
title: 'Feeds',
title: t('opds.feeds'),
headerTransparent: Platform.OS === 'ios',
headerBlurEffect: IS_IOS_26_PLUS ? undefined : 'regular',
headerLargeTitle: Platform.OS === 'ios',
Expand Down
7 changes: 4 additions & 3 deletions apps/expo/app/opds-legacy/[id]/(tabs)/feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import RefreshControl from '~/components/RefreshControl'
import { FullScreenLoader } from '~/components/ui'
import { useOPDSLegacyFeedContext } from '~/context/opdsLegacy'
import { ON_END_REACHED_THRESHOLD } from '~/lib/constants'
import { useLegacyOPDSFeed } from '~/lib/hooks'
import { useLegacyOPDSFeed, useTranslate } from '~/lib/hooks'
import { useDynamicHeader } from '~/lib/hooks/useDynamicHeader'

export default function Screen() {
const { t } = useTranslate()
const { activeServer } = useActiveServer()
const { catalogMeta } = useOPDSLegacyFeedContext()
const {
Expand All @@ -35,7 +36,7 @@ export default function Screen() {
const { numColumns } = useLegacyOPDSEntrySize()

useDynamicHeader({
title: activeServer?.name || 'OPDS Feed',
title: activeServer?.name || t('opds.feedTitle'),
headerLeft: () => <BackLink />,
headerRight: () => <OPDSLegacyFeedActionMenu />,
})
Expand All @@ -46,7 +47,7 @@ export default function Screen() {
}
}

if (showLoader) return <FullScreenLoader label="Loading..." />
if (showLoader) return <FullScreenLoader label={t('opds.loading')} />

if (isLoading) return null

Expand Down
9 changes: 5 additions & 4 deletions apps/expo/app/opds-legacy/[id]/(tabs)/search/[query].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import RefreshControl from '~/components/RefreshControl'
import { FullScreenLoader } from '~/components/ui'
import { useOPDSLegacyFeedContext } from '~/context/opdsLegacy'
import { ON_END_REACHED_THRESHOLD } from '~/lib/constants'
import { useLegacyOPDSFeed } from '~/lib/hooks'
import { useLegacyOPDSFeed, useTranslate } from '~/lib/hooks'
import { useDynamicHeader } from '~/lib/hooks/useDynamicHeader'
import { constructLegacySearchURL } from '~/lib/opdsUtils'

export default function Screen() {
const { t } = useTranslate()
const { query } = useLocalSearchParams<{ query: string }>()
const { searchDoc } = useOPDSLegacyFeedContext()
const { numColumns } = useLegacyOPDSEntrySize()
Expand All @@ -42,7 +43,7 @@ export default function Screen() {
const showLoader = useShowSlowLoader(isLoading)

useDynamicHeader({
title: query || 'Search Results',
title: query || t('opds.searchResults'),
headerLeft: () => <BackLink />,
headerRight: () => <OPDSLegacyFeedActionMenu />,
})
Expand All @@ -53,14 +54,14 @@ export default function Screen() {
}
}

if (showLoader) return <FullScreenLoader label="Loading..." />
if (showLoader) return <FullScreenLoader label={t('opds.loading')} />

if (isLoading) return null

if (!feed || !!error) return <MaybeErrorLegacyFeed error={error} onRetry={onRefetch} />

if (!entries.length) {
return <EmptyState title="Empty Feed" message="Your search returned no results" />
return <EmptyState title={t('opds.emptyFeed')} message={t('opds.searchNoResults')} />
}

return (
Expand Down
4 changes: 3 additions & 1 deletion apps/expo/app/opds-legacy/[id]/(tabs)/search/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Stack } from 'expo-router'
import { Platform } from 'react-native'

import { IS_IOS_26_PLUS } from '~/lib/constants'
import { useTranslate } from '~/lib/hooks'
import { usePreferencesStore } from '~/stores'

export default function Layout() {
const { t } = useTranslate()
const animationEnabled = usePreferencesStore((state) => !state.reduceAnimations)

return (
Expand All @@ -18,7 +20,7 @@ export default function Layout() {
name="index"
options={{
headerShown: true,
title: 'Search',
title: t('opds.search'),
headerTransparent: Platform.OS === 'ios',
headerBlurEffect: IS_IOS_26_PLUS ? undefined : 'regular',
}}
Expand Down
12 changes: 7 additions & 5 deletions apps/expo/app/opds-legacy/[id]/(tabs)/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { SearchHistoryAndFavorites } from '~/components/search/SearchHistoryAndF
import { Text } from '~/components/ui'
import { useOPDSLegacyFeedContext } from '~/context/opdsLegacy'
import { IS_IOS_26_PLUS, useColors } from '~/lib/constants'
import { useTranslate } from '~/lib/hooks'
import { useSearchStore } from '~/stores/search'

export default function Screen() {
const { t } = useTranslate()
const {
activeServer: { id: serverID },
} = useActiveServer()
Expand Down Expand Up @@ -53,7 +55,7 @@ export default function Screen() {
headerTransparent: Platform.OS === 'ios',
headerBlurEffect: IS_IOS_26_PLUS ? undefined : 'regular',
headerSearchBarOptions: {
placeholder: 'Search',
placeholder: t('opds.search'),
onChangeText: (e: NativeSyntheticEvent<TextInputChangeEventData>) =>
setQuery(e.nativeEvent.text),
shouldShowHintSearchIcon: true,
Expand All @@ -67,7 +69,7 @@ export default function Screen() {
textColor: colors.foreground.DEFAULT,
},
})
}, [navigation, setQuery, onSearch, colors])
}, [navigation, setQuery, onSearch, colors, t])

if (!isInputFocused) {
return (
Expand All @@ -76,11 +78,11 @@ export default function Screen() {

<View className="gap-2 px-4 tablet:max-w-lg">
<Text size="xl" className="font-semibold leading-tight text-center">
Search the feed
{t('opds.searchFeed')}
</Text>

<Text size="lg" className="text-center text-foreground-muted">
Enter a search query to find content in this OPDS feed
<Text size="lg" className="text-foreground-muted text-center">
{t('opds.searchFeedDescription')}
</Text>
</View>
</View>
Expand Down
9 changes: 6 additions & 3 deletions apps/expo/app/opds-legacy/[id]/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import {
} from '~/context/opdsLegacy'
import { getOPDSInstance, isOPDSAuthError } from '~/lib/sdk/auth'
import { usePreferencesStore, useSavedServers } from '~/stores'
import { useTranslate } from '~/lib/hooks'
import { useCacheStore } from '~/stores/cache'

type OPDSFeedProviderProps = {
children: React.ReactNode
}

function OPDSFeedProvider({ children }: OPDSFeedProviderProps) {
const { t } = useTranslate()
const { sdk } = useSDK()
const { activeServer } = useActiveServer()
const { onUnauthenticatedResponse } = useClientContext()
Expand Down Expand Up @@ -75,7 +77,7 @@ function OPDSFeedProvider({ children }: OPDSFeedProviderProps) {
)

if (isCatalogLoading && !catalog) {
return <FullScreenLoader label="Loading feed..." />
return <FullScreenLoader label={t('opds.loadingFeed')} />
}

return (
Expand All @@ -92,6 +94,7 @@ function OPDSFeedProvider({ children }: OPDSFeedProviderProps) {
// minimal smart phones where theres just a few buttons for the essentials etc. Definitely something to revisit
// for the v2 flow, as I think that can be much prettier than it currently is
export default function Screen() {
const { t } = useTranslate()
const animationEnabled = usePreferencesStore((state) => !state.reduceAnimations)

const { savedServers, getServerConfig } = useSavedServers()
Expand Down Expand Up @@ -132,8 +135,8 @@ export default function Screen() {

const onAuthError = useCallback(() => {
removeInstanceFromCache(`${serverID}-opds`)
throw new Error('This OPDS server requires authentication')
}, [serverID, removeInstanceFromCache])
throw new Error(t('opds.authenticationRequired'))
}, [serverID, removeInstanceFromCache, t])

if (!activeServer) {
// @ts-expect-error: It's fine
Expand Down
6 changes: 4 additions & 2 deletions apps/expo/app/opds-legacy/[id]/read.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useResolveURL } from '~/components/opds/utils'
import { OPDSLegacyStreamingContextValue } from '~/context/opdsLegacy'
import { db, downloadedFiles, readProgress, syncStatus } from '~/db'
import { useReaderStore } from '~/stores'
import { useTranslate } from '~/lib/hooks'
import { useBookPreferences, useBookTimer } from '~/stores/reader'

type Params = Omit<OPDSLegacyStreamingContextValue, 'pageCount' | 'serverLastRead'> & {
Expand All @@ -21,6 +22,7 @@ type Params = Omit<OPDSLegacyStreamingContextValue, 'pageCount' | 'serverLastRea
}

export default function Screen() {
const { t } = useTranslate()
useKeepAwake()

const params = useLocalSearchParams<Params>()
Expand Down Expand Up @@ -69,10 +71,10 @@ export default function Screen() {
if (streamingURL.includes('{pageNumber}')) {
return streamingURL.replace('{pageNumber}', actualPageNumber.toString())
} else {
throw new Error('Unsupported streaming URL format')
throw new Error(t('opds.unsupportedStreamingUrl'))
}
},
[resolveUrl, contextValue.streamingURL],
[resolveUrl, contextValue.streamingURL, t],
)

const book = useMemo(
Expand Down
Loading