i18n(client): translate book filter, operator, and sort field labels

Replace hardcoded English filter, operator, and sort field labels with dynamic i18n lookup helpers that resolve against the active locale catalog.

Closes #807
This commit is contained in:
neon
2026-07-30 19:51:13 +00:00
parent 624dce34a8
commit e7ccf6648c
16 changed files with 819 additions and 112 deletions
@@ -12,7 +12,7 @@ import {
type RuleOperator,
} from '@bookorbit/types'
import { READ_STATUSES } from '@bookorbit/types'
import { FIELD_LABELS, OPERATOR_LABELS } from '@/features/book/lib/filter-labels'
import { fieldLabel, operatorLabel } from '@/features/book/lib/filter-labels'
import { providerIconPathSafe } from '@/features/book/lib/provider-icons'
import { PROVIDER_SHORT_LABELS } from '@/lib/provider-colors'
import { useLibraries } from '@/features/library/composables/useLibraries'
@@ -394,7 +394,7 @@ function showValueToInput(operator: RuleOperator): boolean {
@change="onFieldChange(index)"
class="h-9 rounded-md border border-input bg-background text-foreground text-sm px-2 focus:outline-none focus:ring-2 focus:ring-primary shrink-0"
>
<option v-for="field in RULE_FIELDS" :key="field" :value="field">{{ FIELD_LABELS[field] }}</option>
<option v-for="field in RULE_FIELDS" :key="field" :value="field">{{ fieldLabel(field) }}</option>
</select>
<select
@@ -402,7 +402,7 @@ function showValueToInput(operator: RuleOperator): boolean {
@change="onOperatorChange(index)"
class="h-9 rounded-md border border-input bg-background text-foreground text-sm px-2 focus:outline-none focus:ring-2 focus:ring-primary shrink-0"
>
<option v-for="op in FIELD_OPERATORS[node.rule.field]" :key="op" :value="op">{{ OPERATOR_LABELS[op] }}</option>
<option v-for="op in FIELD_OPERATORS[node.rule.field]" :key="op" :value="op">{{ operatorLabel(op) }}</option>
</select>
<div
@@ -417,7 +417,7 @@ function showValueToInput(operator: RuleOperator): boolean {
/>
<select
v-model="node.rule.provider"
class="h-7 bg-transparent text-foreground text-sm outline-none"
class="h-7 bg-background text-foreground text-sm outline-none"
:aria-label="t('book.filter.communityRatingProvider')"
@change="emitUpdate"
>
@@ -454,7 +454,7 @@ function showValueToInput(operator: RuleOperator): boolean {
</span>
<select
:value="''"
class="h-7 flex-1 min-w-32 bg-transparent text-foreground text-sm outline-none"
class="h-7 flex-1 min-w-32 bg-background text-foreground text-sm outline-none"
@change="addLibraryChip(index, $event)"
>
<option value="" disabled>
@@ -490,11 +490,7 @@ function showValueToInput(operator: RuleOperator): boolean {
<X :size="10" />
</button>
</span>
<select
:value="''"
class="h-7 flex-1 min-w-32 bg-transparent text-foreground text-sm outline-none"
@change="addStatusChip(index, $event)"
>
<select :value="''" class="h-7 flex-1 min-w-32 bg-background text-foreground text-sm outline-none" @change="addStatusChip(index, $event)">
<option value="" disabled>{{ t('book.filter.selectStatus') }}</option>
<option v-for="status in READ_STATUSES" :key="status" :value="status" :disabled="node.rule.valueChips.includes(status)">
{{ READ_STATUS_LABELS[status] ?? status }}
@@ -3,7 +3,7 @@ import { computed } from 'vue'
import { ArrowDownAZ, ArrowUpAZ, ChevronDown, ChevronUp, Plus, Trash2 } from '@lucide/vue'
import type { SortField, SortSpec } from '@bookorbit/types'
import { SORT_FIELDS } from '@bookorbit/types'
import { SORT_FIELD_LABELS } from '@/features/book/lib/filter-labels'
import { sortFieldLabel } from '@/features/book/lib/filter-labels'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useI18n } from 'vue-i18n'
@@ -95,7 +95,7 @@ const canAddMore = computed(() => props.modelValue.length < SORT_FIELDS.length)
class="flex-1 h-8 rounded-md border border-input bg-background text-foreground text-sm px-2 focus:outline-none focus:ring-2 focus:ring-primary"
>
<option v-for="field in availableFields(spec.field)" :key="field" :value="field">
{{ SORT_FIELD_LABELS[field] }}
{{ sortFieldLabel(field) }}
</option>
</select>
<Tooltip>
@@ -38,7 +38,7 @@ import type { BookCard, Rule, SortSpec, TableLayoutState, TableViewType } from '
import { BOOK_METADATA_LOCK_FIELDS } from '@bookorbit/types'
import { isBookPlaceholder, type BookSlot } from '@/features/book/composables/useBookWindow'
import { useNarratorSearch } from '@/features/book/composables/useNarratorSearch'
import { SORT_FIELD_LABELS } from '@/features/book/lib/filter-labels'
import { sortFieldLabel } from '@/features/book/lib/filter-labels'
import { useDisplaySettings } from '@/composables/useDisplaySettings'
import { useActiveCustomFields } from '@/features/book/composables/useActiveCustomFields'
import { useI18n } from 'vue-i18n'
@@ -401,7 +401,7 @@ const activeSorts = computed(() =>
props.sort.map((s) => ({
field: s.field,
dir: s.dir,
label: SORT_FIELD_LABELS[s.field] ?? s.field,
label: sortFieldLabel(s.field),
})),
)
@@ -1,6 +1,6 @@
import { computed, watch, type Ref } from 'vue'
import type { SortSpec } from '@bookorbit/types'
import { SORT_FIELD_LABELS } from '../lib/filter-labels'
import { sortFieldLabel } from '../lib/filter-labels'
const DEFAULT_SORT: SortSpec[] = [{ field: 'title', dir: 'asc' }]
@@ -45,7 +45,7 @@ export function useViewSort(sort: Ref<SortSpec[]>, keyPrefix: string, entityId:
const isDefaultSort = computed(() => sort.value.length === 1 && sort.value[0]?.field === 'title' && sort.value[0]?.dir === 'asc')
const sortSummary = computed(() => sort.value.map((s) => `${SORT_FIELD_LABELS[s.field]} ${s.dir === 'asc' ? '↑' : '↓'}`).join(', '))
const sortSummary = computed(() => sort.value.map((s) => `${sortFieldLabel(s.field)} ${s.dir === 'asc' ? '↑' : '↓'}`).join(', '))
return { sortModel, isDefaultSort, sortSummary, resetSort, saveSort }
}
@@ -1,31 +1,74 @@
import { describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it } from 'vitest'
import type { Rule } from '@bookorbit/types'
import { FIELD_LABELS, OPERATOR_LABELS, ruleToParts } from '../filter-labels'
import { RULE_FIELDS, RULE_OPERATORS, SORT_FIELDS } from '@bookorbit/types'
import { activateI18nLocale, i18n } from '@/i18n'
import { compileIcuCatalog } from '@/i18n/icu'
import en from '@/locales/en.json'
import { fieldLabel, operatorLabel, ruleToParts, sortFieldLabel } from '../filter-labels'
describe('catalog coverage', () => {
it('translates every rule field', () => {
for (const field of RULE_FIELDS) {
expect(fieldLabel(field)).not.toBe(`book.filter.fields.${field}`)
}
})
it('translates every rule operator', () => {
for (const operator of RULE_OPERATORS) {
expect(operatorLabel(operator)).not.toBe(`book.filter.operators.${operator}`)
}
})
it('translates every sort field', () => {
for (const field of SORT_FIELDS) {
expect(sortFieldLabel(field)).not.toBe(`book.sort.fields.${field}`)
}
})
})
describe('active locale', () => {
afterEach(() => {
activateI18nLocale('en')
})
it('resolves labels from the active locale instead of hardcoded English', () => {
const catalog = structuredClone(en)
catalog.book.filter.fields.title = 'Titel'
catalog.book.filter.operators.contains = 'enthält'
catalog.book.sort.fields.title = 'Titel'
i18n.global.setLocaleMessage('de', compileIcuCatalog(catalog, 'de'))
activateI18nLocale('de')
expect(fieldLabel('title')).toBe('Titel')
expect(operatorLabel('contains')).toBe('enthält')
expect(sortFieldLabel('title')).toBe('Titel')
})
})
describe('lock status labels', () => {
it('exposes a Lock Status field label', () => {
expect(FIELD_LABELS.lockStatus).toBe('Lock Status')
expect(fieldLabel('lockStatus')).toBe('Lock Status')
})
it('exposes is locked / is unlocked operator labels', () => {
expect(OPERATOR_LABELS.isLocked).toBe('is locked')
expect(OPERATOR_LABELS.isUnlocked).toBe('is unlocked')
expect(operatorLabel('isLocked')).toBe('is locked')
expect(operatorLabel('isUnlocked')).toBe('is unlocked')
})
})
describe('series status labels', () => {
it('exposes a Series Status field label', () => {
expect(FIELD_LABELS.seriesStatus).toBe('Series Status')
expect(fieldLabel('seriesStatus')).toBe('Series Status')
})
it('exposes an is up next operator label', () => {
expect(OPERATOR_LABELS.isUpNext).toBe('is up next')
expect(operatorLabel('isUpNext')).toBe('is up next')
})
})
describe('community rating labels', () => {
it('exposes a Community Rating field label', () => {
expect(FIELD_LABELS.communityRating).toBe('Community Rating')
expect(fieldLabel('communityRating')).toBe('Community Rating')
})
})
+16 -86
View File
@@ -1,89 +1,22 @@
import type { Rule, RuleField, RuleOperator, SortField } from '@bookorbit/types'
import { i18n } from '@/i18n'
import { PROVIDER_SHORT_LABELS } from '@/lib/provider-colors'
export const SORT_FIELD_LABELS: Record<SortField, string> = {
author: 'Author',
title: 'Title',
series: 'Series',
seriesIndex: 'Series #',
addedAt: 'Date Added',
updatedAt: 'Date Updated',
publishedDate: 'Published Date',
publishedYear: 'Published Year',
pageCount: 'Page Count',
rating: 'Rating',
publisher: 'Publisher',
fileSize: 'File Size',
readProgress: 'Read Progress',
readStatus: 'Read Status',
format: 'Format',
language: 'Language',
metadataScore: 'Metadata Score',
lastReadAt: 'Last Read',
startedAt: 'Date Started',
finishedAt: 'Date Finished',
random: 'Random',
// Resolved through the global composer rather than useI18n() because these helpers also run
// outside a component setup (useViewSort, plain computeds). Composer.t tracks the active locale,
// so computeds and templates calling these still re-evaluate on a language change.
const t = i18n.global.t
export function sortFieldLabel(field: SortField): string {
return t(`book.sort.fields.${field}`)
}
export const FIELD_LABELS: Record<RuleField, string> = {
title: 'Title',
publisher: 'Publisher',
language: 'Language',
series: 'Series',
seriesIndex: 'Series Index',
publishedDate: 'Published Date',
publishedYear: 'Published Year',
pageCount: 'Page Count',
author: 'Author',
genre: 'Genre',
tag: 'Tag',
collection: 'Collection',
library: 'Library',
format: 'Format',
addedAt: 'Added Date',
startedAt: 'Date Started',
finishedAt: 'Date Finished',
fileAvailability: 'File Availability',
rating: 'Rating',
communityRating: 'Community Rating',
readProgress: 'Reading Progress',
readStatus: 'Read Status',
description: 'Description',
isbn: 'ISBN',
metadataScore: 'Metadata Score',
cover: 'Cover',
lockStatus: 'Lock Status',
seriesStatus: 'Series Status',
export function fieldLabel(field: RuleField): string {
return t(`book.filter.fields.${field}`)
}
export const OPERATOR_LABELS: Record<RuleOperator, string> = {
contains: 'contains',
notContains: 'does not contain',
startsWith: 'starts with',
endsWith: 'ends with',
eq: 'is',
notEq: 'is not',
isEmpty: 'is empty',
isNotEmpty: 'is not empty',
gt: 'greater than',
gte: 'at least',
lt: 'less than',
lte: 'at most',
between: 'between',
includesAny: 'includes any of',
includesAll: 'includes all of',
excludesAll: 'excludes all of',
before: 'before',
after: 'after',
withinLast: 'within last',
isMissing: 'is missing',
isPresent: 'is present',
isUnread: 'is unread',
isInProgress: 'is in progress',
isFinished: 'is finished',
isLocked: 'is locked',
isUnlocked: 'is unlocked',
isUpNext: 'is up next',
export function operatorLabel(operator: RuleOperator): string {
return t(`book.filter.operators.${operator}`)
}
const NO_VALUE_OPS: RuleOperator[] = [
@@ -102,18 +35,15 @@ const NO_VALUE_OPS: RuleOperator[] = [
function communityRatingProviderLabel(rule: Rule): string {
if (rule.field !== 'communityRating') return ''
const provider = rule.provider ?? 'any'
if (provider === 'any') return 'Any provider'
if (provider === 'any') return t('book.filter.anyProvider')
return PROVIDER_SHORT_LABELS[provider] ?? provider
}
export function ruleToParts(rule: Rule): { field: string; operator: string; value: string | null } {
const field =
rule.field === 'communityRating'
? `${FIELD_LABELS[rule.field] ?? rule.field} (${communityRatingProviderLabel(rule)})`
: (FIELD_LABELS[rule.field] ?? rule.field)
const operator = OPERATOR_LABELS[rule.operator] ?? rule.operator
const field = rule.field === 'communityRating' ? `${fieldLabel(rule.field)} (${communityRatingProviderLabel(rule)})` : fieldLabel(rule.field)
const operator = operatorLabel(rule.operator)
if (NO_VALUE_OPS.includes(rule.operator)) return { field, operator, value: null }
if (rule.operator === 'withinLast') return { field, operator, value: `${rule.value} days` }
if (rule.operator === 'withinLast') return { field, operator, value: `${rule.value} ${t('book.filter.unit.days')}` }
const val = Array.isArray(rule.value) ? (rule.value as string[]).join(', ') : String(rule.value ?? '')
return { field, operator, value: rule.valueTo !== undefined ? `${val} - ${rule.valueTo}` : val }
}
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Nach unten verschieben",
"ascending": "Aufsteigend",
"descending": "Absteigend",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Entfernen",
"emptyState": "Keine Sortierung konfiguriert. Titel aufsteigend wird verwendet.",
"addLevel": "Sortierebene hinzufügen"
@@ -3249,6 +3272,65 @@
"all": "ALLE",
"any": "BELIEBIG",
"ofFollowingRules": "der folgenden Regeln",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Beliebiger Anbieter",
"communityRatingProvider": "Anbieter der Community-Bewertung",
"loadingLibraries": "Bibliotheken werden geladen ...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Move down",
"ascending": "Ascending",
"descending": "Descending",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Remove",
"emptyState": "No sort configured. Title ascending will be used.",
"addLevel": "Add sort level"
@@ -3249,6 +3272,65 @@
"all": "ALL",
"any": "ANY",
"ofFollowingRules": "of the following rules",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Any provider",
"communityRatingProvider": "Community rating provider",
"loadingLibraries": "Loading libraries...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Bajar",
"ascending": "Ascendente",
"descending": "Descendente",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Quitar",
"emptyState": "No hay clasificación configurada. Se utilizará título ascendente.",
"addLevel": "Agregar nivel de clasificación"
@@ -3249,6 +3272,65 @@
"all": "TODOS",
"any": "CUALQUIER",
"ofFollowingRules": "de las siguientes reglas",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Cualquier proveedor",
"communityRatingProvider": "Proveedor de calificación comunitaria",
"loadingLibraries": "Cargando bibliotecas...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Descendre",
"ascending": "Ascendant",
"descending": "Décroissant",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Supprimer",
"emptyState": "Aucun tri configuré. Le titre ascendant sera utilisé.",
"addLevel": "Ajouter un niveau de tri"
@@ -3249,6 +3272,65 @@
"all": "TOUS",
"any": "TOUT",
"ofFollowingRules": "des règles suivantes",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "N'importe quel fournisseur",
"communityRatingProvider": "Fournisseur de notation communautaire",
"loadingLibraries": "Chargement des bibliothèques...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Spostati giù",
"ascending": "Ascendente",
"descending": "Discendente",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Rimuovi",
"emptyState": "Nessun ordinamento configurato. Verrà utilizzato il titolo crescente.",
"addLevel": "Aggiungi livello di ordinamento"
@@ -3249,6 +3272,65 @@
"all": "TUTTI",
"any": "QUALSIASI",
"ofFollowingRules": "delle seguenti regole",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Qualsiasi fornitore",
"communityRatingProvider": "Fornitore di valutazioni della comunità",
"loadingLibraries": "Caricamento librerie...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Omlaag verplaatsen",
"ascending": "Oplopend",
"descending": "Aflopend",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Verwijderen",
"emptyState": "Geen sortering ingesteld. Titel oplopend wordt gebruikt.",
"addLevel": "Sorteerniveau toevoegen"
@@ -3249,6 +3272,65 @@
"all": "ALLE",
"any": "ELKE",
"ofFollowingRules": "van de volgende regels",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Elke aanbieder",
"communityRatingProvider": "Aanbieder van communitybeoordeling",
"loadingLibraries": "Bibliotheken laden...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Przesuń w dół",
"ascending": "Rosnąco",
"descending": "Malejąco",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Usuń",
"emptyState": "Nie skonfigurowano sortowania. Tytuł będzie używany w kolejności rosnącej.",
"addLevel": "Dodaj poziom sortowania"
@@ -3249,6 +3272,65 @@
"all": "WSZYSTKIE",
"any": "KAŻDY",
"ofFollowingRules": "poniższych zasad",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Dowolny dostawca",
"communityRatingProvider": "Dostawca ocen społecznościowych",
"loadingLibraries": "Ładowanie bibliotek...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Mover para baixo",
"ascending": "Crescente",
"descending": "Decrescente",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Remover",
"emptyState": "Nenhuma ordenação configurada. Ordem alfabética por título (crescente) será utilizada.",
"addLevel": "Adicionar nível de ordenação"
@@ -3249,6 +3272,65 @@
"all": "TODAS",
"any": "QUALQUER",
"ofFollowingRules": "das seguintes regras",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Qualquer provedor",
"communityRatingProvider": "Provedor de avaliação da comunidade",
"loadingLibraries": "Carregando bibliotecas...",
+82
View File
@@ -3240,6 +3240,29 @@
"moveDown": "Premakni navzdol",
"ascending": "Naraščajoče",
"descending": "Padajoče",
"fields": {
"author": "Author",
"title": "Title",
"series": "Series",
"seriesIndex": "Series #",
"addedAt": "Date Added",
"updatedAt": "Date Updated",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"rating": "Rating",
"publisher": "Publisher",
"fileSize": "File Size",
"readProgress": "Read Progress",
"readStatus": "Read Status",
"format": "Format",
"lastReadAt": "Last Read",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"random": "Random",
"language": "Language",
"metadataScore": "Metadata Score"
},
"remove": "Odstrani",
"emptyState": "Razvrščanje ni nastavljeno. Uporabljen bo naslov naraščajoče.",
"addLevel": "Dodaj raven razvrščanja"
@@ -3249,6 +3272,65 @@
"all": "VSE",
"any": "KATERO KOLI",
"ofFollowingRules": "naslednjih pravil",
"fields": {
"title": "Title",
"publisher": "Publisher",
"language": "Language",
"series": "Series",
"seriesIndex": "Series Index",
"publishedDate": "Published Date",
"publishedYear": "Published Year",
"pageCount": "Page Count",
"author": "Author",
"genre": "Genre",
"tag": "Tag",
"collection": "Collection",
"library": "Library",
"format": "Format",
"addedAt": "Added Date",
"startedAt": "Date Started",
"finishedAt": "Date Finished",
"fileAvailability": "File Availability",
"rating": "Rating",
"communityRating": "Community Rating",
"readProgress": "Reading Progress",
"readStatus": "Read Status",
"description": "Description",
"isbn": "ISBN",
"metadataScore": "Metadata Score",
"cover": "Cover",
"lockStatus": "Lock Status",
"seriesStatus": "Series Status"
},
"operators": {
"contains": "contains",
"notContains": "does not contain",
"startsWith": "starts with",
"endsWith": "ends with",
"eq": "is",
"notEq": "is not",
"isEmpty": "is empty",
"isNotEmpty": "is not empty",
"gt": "greater than",
"gte": "at least",
"lt": "less than",
"lte": "at most",
"between": "between",
"includesAny": "includes any of",
"includesAll": "includes all of",
"excludesAll": "excludes all of",
"before": "before",
"after": "after",
"withinLast": "within last",
"isMissing": "is missing",
"isPresent": "is present",
"isUnread": "is unread",
"isInProgress": "is in progress",
"isFinished": "is finished",
"isLocked": "is locked",
"isUnlocked": "is unlocked",
"isUpNext": "is up next"
},
"anyProvider": "Kateri koli ponudnik",
"communityRatingProvider": "Ponudnik ocen skupnosti",
"loadingLibraries": "Nalaganje knjižnic ...",
+2 -2
View File
@@ -47,7 +47,7 @@ import { useEffectiveViewMode } from '@/composables/useEffectiveViewMode'
import { useViewDisplaySettings } from '@/composables/useViewDisplaySettings'
import { useViewSearch } from '@/features/book/composables/useViewSearch'
import FilterSummary from '@/features/book/components/FilterSummary.vue'
import { SORT_FIELD_LABELS } from '@/features/book/lib/filter-labels'
import { sortFieldLabel } from '@/features/book/lib/filter-labels'
import { DEFAULT_COVER_ASPECT_RATIO } from '@/features/book/lib/cover-aspect-ratio'
import { usePageTitle } from '@/composables/usePageTitle'
import { useBookNavigation } from '@/features/book/composables/useBookNavigation'
@@ -141,7 +141,7 @@ usePageTitle(pageTitle)
const sortChip = computed(() => {
const specs = smartScope.value?.defaultSort
if (!specs?.length) return null
return specs.map((s) => `${SORT_FIELD_LABELS[s.field as SortField] ?? s.field} ${s.dir === 'asc' ? '↑' : '↓'}`).join(', ')
return specs.map((s) => `${sortFieldLabel(s.field as SortField)} ${s.dir === 'asc' ? '↑' : '↓'}`).join(', ')
})
const filterExpanded = smartScopeFilterExpanded