mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 429a25faa9 | |||
| 486a2eeca3 |
@@ -712,4 +712,33 @@ describe('AddonManager', () => {
|
||||
expect(roadtrip.querySelector('svg.lucide-car-front')).toBeInTheDocument();
|
||||
expect(tours.querySelector('svg.lucide-puzzle')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-ADMIN-ADDON-045: finds an addon by name or description and shows the ones on or off', async () => {
|
||||
const user = userEvent.setup();
|
||||
server.use(
|
||||
addonsRoute([
|
||||
buildAddon({ id: 'x-lists', name: 'Todo List', description: 'Track tasks', type: 'trip', enabled: true }),
|
||||
buildAddon({ id: 'x-costs', name: 'Budget', description: 'Split costs', type: 'trip', enabled: false }),
|
||||
buildAddon({ id: 'x-holiday', name: 'Vacay', description: 'Holiday planner', type: 'global', enabled: true }),
|
||||
])
|
||||
);
|
||||
render(<AddonManager />);
|
||||
await screen.findByText('Todo List');
|
||||
|
||||
await user.type(screen.getByRole('searchbox', { name: 'Search addons' }), 'costs');
|
||||
expect(screen.getByText('Budget')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Todo List')).toBeNull();
|
||||
expect(screen.queryByText('Vacay')).toBeNull();
|
||||
|
||||
await user.clear(screen.getByRole('searchbox', { name: 'Search addons' }));
|
||||
await user.click(screen.getByRole('button', { name: 'On' }));
|
||||
expect(screen.getByText('Todo List')).toBeInTheDocument();
|
||||
expect(screen.getByText('Vacay')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Budget')).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Off' }));
|
||||
await user.type(screen.getByRole('searchbox', { name: 'Search addons' }), 'nothing like this');
|
||||
expect(screen.getByText('No addon matches the search or the filter.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComponentType } from 'react'
|
||||
import { useState, type ComponentType } from 'react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useIsDark } from '../../hooks/useIsDark'
|
||||
import { Puzzle, ListChecks, Wallet, FileText, CalendarDays, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, Sparkles, Luggage, Plane, Bookmark, Users, Loader2, Mountain, CarFront } from 'lucide-react'
|
||||
import { Puzzle, ListChecks, Wallet, FileText, CalendarDays, Globe, Briefcase, Image, Terminal, Link2, Compass, BookOpen, Sparkles, Luggage, Plane, Bookmark, Users, Loader2, Mountain, CarFront, Search } from 'lucide-react'
|
||||
import EmptyState from '../shared/EmptyState'
|
||||
import DawarichIcon from '../shared/DawarichIcon'
|
||||
import AirTrailIcon from '../shared/AirTrailIcon'
|
||||
@@ -76,6 +76,8 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
|
||||
toggleToursWanderer,
|
||||
mapSwitches,
|
||||
} = useAddonManager()
|
||||
const [query, setQuery] = useState('')
|
||||
const [view, setView] = useState<AddonView>('all')
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -194,10 +196,10 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
|
||||
)
|
||||
}
|
||||
|
||||
/* One column per type, side by side, instead of three stacked sections. Stacked, each
|
||||
section opened its own set of columns and the tall tiles (Collab, Journey) set a
|
||||
section's height while its neighbours ran out early. Side by side the column's
|
||||
head carries the type, so the tiles need no badge of their own. */
|
||||
/* A card per type, the way every other admin tab sets its cards: Trip in the
|
||||
left column, Global and Integration in the right one, each card with the
|
||||
dialogs' head band carrying the type, its count and what it means. Above
|
||||
them one card says how many are on and finds an addon by name. */
|
||||
const groups = [
|
||||
{ key: 'trip', addons: tripAddons },
|
||||
{ key: 'global', addons: globalAddons },
|
||||
@@ -214,68 +216,145 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
icon={Puzzle}
|
||||
title={t('admin.addons.title')}
|
||||
hint={subtitle}
|
||||
badge={totalCount > 0 ? (
|
||||
<Tooltip label={t('admin.addons.group.count', { enabled: enabledCount, total: totalCount })}>
|
||||
<span className="inline-flex">
|
||||
<StatusPill tone={enabledCount > 0 ? 'success' : 'neutral'}>{enabledCount}/{totalCount}</StatusPill>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : undefined}
|
||||
>
|
||||
{addons.length === 0 ? (
|
||||
<EmptyState scene="idle" title={t('admin.addons.noAddons')} surface="var(--bg-secondary)" />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 items-start gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{groups.map(g => {
|
||||
const meta = TYPE_META[g.key]
|
||||
return (
|
||||
<section key={g.key} className="min-w-0 overflow-hidden rounded-[14px] border border-edge-faint bg-surface-card">
|
||||
<GroupHead icon={meta.icon} label={t(meta.labelKey)} hint={t(meta.hintKey)} addons={g.key === 'integration' ? [...aiAddons, ...g.addons] : g.addons} t={t} />
|
||||
<div className="divide-y divide-edge-faint">
|
||||
{/* The AI addons share a connection and a settings dialog, so they head the column as one tile. */}
|
||||
{g.key === 'integration' && aiAddons.length > 0 && <AiAddonTile addons={aiAddons} onToggle={handleToggle} />}
|
||||
{g.addons.map(tile)}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
/** Whether an addon is shown under the search and the filter. */
|
||||
const shown = (addon: Addon) => {
|
||||
if (view === 'on' && !addon.enabled) return false
|
||||
if (view === 'off' && addon.enabled) return false
|
||||
const q = query.trim().toLocaleLowerCase()
|
||||
if (!q) return true
|
||||
const label = getAddonLabel(t, addon)
|
||||
return `${label.name} ${label.description ?? ''}`.toLocaleLowerCase().includes(q)
|
||||
}
|
||||
|
||||
const card = (g: (typeof groups)[number]) => {
|
||||
const meta = TYPE_META[g.key]
|
||||
const all = g.key === 'integration' ? [...aiAddons, ...g.addons] : g.addons
|
||||
const visible = g.addons.filter(shown)
|
||||
const ai = g.key === 'integration' && aiAddons.some(shown)
|
||||
if (!visible.length && !ai) return null
|
||||
const on = all.filter(a => a.enabled).length
|
||||
return (
|
||||
<SettingsCard
|
||||
key={g.key}
|
||||
icon={meta.icon}
|
||||
title={t(meta.labelKey)}
|
||||
hint={t(meta.hintKey)}
|
||||
badge={(
|
||||
<Tooltip label={t('admin.addons.group.count', { enabled: on, total: all.length })}>
|
||||
<span className="inline-flex"><StatusPill tone={on > 0 ? 'success' : 'neutral'}>{on}/{all.length}</StatusPill></span>
|
||||
</Tooltip>
|
||||
)}
|
||||
bodyClassName="p-3"
|
||||
>
|
||||
<div className="divide-y divide-edge-faint overflow-hidden rounded-[12px] border border-edge-faint bg-surface-card">
|
||||
{/* The AI addons share a connection and a settings dialog, so they head the card as one tile. */}
|
||||
{ai && <AiAddonTile addons={aiAddons} onToggle={handleToggle} />}
|
||||
{visible.map(tile)}
|
||||
</div>
|
||||
)}
|
||||
</SettingsCard>
|
||||
</SettingsCard>
|
||||
)
|
||||
}
|
||||
|
||||
const [first, ...rest] = groups
|
||||
const cards = { start: first ? card(first) : null, end: rest.map(card).filter(Boolean) }
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsCard
|
||||
icon={Puzzle}
|
||||
title={t('admin.addons.title')}
|
||||
hint={subtitle}
|
||||
badge={totalCount > 0 ? (
|
||||
<Tooltip label={t('admin.addons.group.count', { enabled: enabledCount, total: totalCount })}>
|
||||
<span className="inline-flex">
|
||||
<StatusPill tone={enabledCount > 0 ? 'success' : 'neutral'}>{enabledCount}/{totalCount}</StatusPill>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : undefined}
|
||||
>
|
||||
{addons.length === 0
|
||||
? <EmptyState scene="idle" title={t('admin.addons.noAddons')} surface="var(--bg-secondary)" />
|
||||
: <AddonToolbar groups={groups.map(g => ({ key: g.key, addons: g.key === 'integration' ? [...aiAddons, ...g.addons] : g.addons }))} query={query} onQuery={setQuery} view={view} onView={setView} t={t} />}
|
||||
</SettingsCard>
|
||||
|
||||
{addons.length > 0 && (cards.start || cards.end.length > 0 ? (
|
||||
<div className="grid grid-cols-1 gap-x-5 xl:grid-cols-2 xl:items-start">
|
||||
<div className="min-w-0">{cards.start}</div>
|
||||
<div className="min-w-0">{cards.end}</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="m-0 rounded-2xl border border-dashed border-edge-faint px-4 py-8 text-center text-content-muted" style={fs(13, 'body')}>
|
||||
{t('admin.addons.noMatch')}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** The head band of one type's column: icon tile, the type, its count and what it means. */
|
||||
function GroupHead({ icon: Icon, label, hint, addons, t }: {
|
||||
icon: ComponentType<{ size?: number; className?: string; strokeWidth?: number }>
|
||||
label: string
|
||||
hint: string
|
||||
addons: Addon[]
|
||||
type AddonView = 'all' | 'on' | 'off'
|
||||
|
||||
/**
|
||||
* The head card's body: a tile per type with how many of it are on, then a
|
||||
* search over the addons' names and descriptions and a switch between all,
|
||||
* the ones on and the ones off.
|
||||
*/
|
||||
function AddonToolbar({ groups, query, onQuery, view, onView, t }: {
|
||||
groups: { key: string; addons: Addon[] }[]
|
||||
query: string
|
||||
onQuery: (q: string) => void
|
||||
view: AddonView
|
||||
onView: (v: AddonView) => void
|
||||
t: (key: string, params?: Record<string, unknown>) => string
|
||||
}) {
|
||||
const enabled = addons.filter(a => a.enabled).length
|
||||
return (
|
||||
<div className="flex items-start gap-2.5 border-b border-edge-faint bg-surface-tertiary px-3.5 py-2.5">
|
||||
<span className="mt-px grid h-7 w-7 flex-none place-items-center rounded-[9px] bg-surface-card text-content-secondary shadow-sm">
|
||||
<Icon size={13} strokeWidth={2} />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="m-0 min-w-0 flex-1 truncate font-bold text-content" style={fs(13, 'body')}>{label}</h3>
|
||||
<Tooltip label={t('admin.addons.group.count', { enabled, total: addons.length })}>
|
||||
<span className="inline-flex">
|
||||
<StatusPill>{enabled}/{addons.length}</StatusPill>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/* Says what the type means: the only place that still explains it. */}
|
||||
<p className="m-0 mt-0.5 leading-snug text-content-faint" style={fs(11)}>{hint}</p>
|
||||
<>
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
{groups.map(g => {
|
||||
const meta = TYPE_META[g.key]
|
||||
const Icon = meta.icon
|
||||
const on = g.addons.filter(a => a.enabled).length
|
||||
return (
|
||||
<div key={g.key} className="flex items-center gap-3 rounded-[12px] border border-edge-faint bg-surface-card px-3 py-2.5">
|
||||
<span className="grid h-8 w-8 flex-none place-items-center rounded-[10px] bg-surface-secondary text-content-secondary">
|
||||
<Icon size={15} />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate font-semibold text-content" style={fs(12.5, 'body')}>{t(meta.labelKey)}</span>
|
||||
<span className="block text-content-faint" style={fs(11)}>{t('admin.addons.group.count', { enabled: on, total: g.addons.length })}</span>
|
||||
</span>
|
||||
<span className={`font-geist font-bold tabular-nums ${on > 0 ? 'text-content' : 'text-content-faint'}`} style={fs(18, 'body')} aria-hidden>{on}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<label className="relative flex min-w-0 flex-1 items-center">
|
||||
<Search size={14} className="pointer-events-none absolute start-3 text-content-faint" />
|
||||
<input
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={e => onQuery(e.target.value)}
|
||||
placeholder={t('admin.addons.search')}
|
||||
aria-label={t('admin.addons.search')}
|
||||
className="h-9 w-full rounded-[10px] border border-edge-faint bg-surface-card ps-8 pe-3 text-content outline-none transition-colors placeholder:text-content-faint focus:border-accent"
|
||||
style={fs(13, 'body')}
|
||||
/>
|
||||
</label>
|
||||
<div className="inline-flex flex-none gap-1 rounded-[10px] bg-surface-tertiary p-1" role="group" aria-label={t('admin.addons.filter')}>
|
||||
{(['all', 'on', 'off'] as const).map(v => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
onClick={() => onView(v)}
|
||||
aria-pressed={view === v}
|
||||
className={`h-7 rounded-[8px] px-3 font-medium transition-colors ${view === v ? 'bg-surface-card text-content shadow-sm' : 'text-content-muted hover:text-content'}`}
|
||||
style={fs(12.5, 'body')}
|
||||
>
|
||||
{t(`admin.addons.filter.${v}`)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -708,6 +708,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'تم تحديث الإضافة',
|
||||
'admin.addons.toast.error': 'فشل تحديث الإضافة',
|
||||
'admin.addons.group.count': '{enabled} من {total} مُفعّل',
|
||||
'admin.addons.search': 'البحث في الإضافات',
|
||||
'admin.addons.filter': 'عرض',
|
||||
'admin.addons.filter.all': 'الكل',
|
||||
'admin.addons.filter.on': 'مفعّلة',
|
||||
'admin.addons.filter.off': 'معطّلة',
|
||||
'admin.addons.noMatch': 'لا توجد إضافة تطابق البحث أو عامل التصفية.',
|
||||
'admin.addons.subOptions': '{count} خيار',
|
||||
'admin.addons.subOptions.zero': 'لا توجد خيارات',
|
||||
'admin.addons.subOptions.one': 'خيار واحد',
|
||||
|
||||
@@ -749,6 +749,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Əlavə yeniləndi',
|
||||
'admin.addons.toast.error': 'Əlavəni yeniləmək mümkün olmadı',
|
||||
'admin.addons.group.count': '{total} əlavədən {enabled} aktivdir',
|
||||
'admin.addons.search': 'Əlavələrdə axtar',
|
||||
'admin.addons.filter': 'Göstər',
|
||||
'admin.addons.filter.all': 'Hamısı',
|
||||
'admin.addons.filter.on': 'Aktiv',
|
||||
'admin.addons.filter.off': 'Deaktiv',
|
||||
'admin.addons.noMatch': 'Axtarışa və ya filtrə uyğun əlavə yoxdur.',
|
||||
'admin.addons.subOptions': '{count} seçim',
|
||||
'admin.addons.subOptions.one': '{count} seçim',
|
||||
'admin.addons.subOptionsOn': '{count} aktiv',
|
||||
|
||||
@@ -657,6 +657,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Complemento atualizado',
|
||||
'admin.addons.toast.error': 'Falha ao atualizar complemento',
|
||||
'admin.addons.group.count': '{enabled} de {total} ativados',
|
||||
'admin.addons.search': 'Pesquisar complementos',
|
||||
'admin.addons.filter': 'Mostrar',
|
||||
'admin.addons.filter.all': 'Todos',
|
||||
'admin.addons.filter.on': 'Ativos',
|
||||
'admin.addons.filter.off': 'Inativos',
|
||||
'admin.addons.noMatch': 'Nenhum complemento corresponde à pesquisa ou ao filtro.',
|
||||
'admin.addons.subOptions': '{count} opções',
|
||||
'admin.addons.subOptions.one': '{count} opção',
|
||||
'admin.addons.subOptionsOn': '{count} ativas',
|
||||
|
||||
@@ -264,6 +264,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Complement actualitzat',
|
||||
'admin.addons.toast.error': "No s'ha pogut actualitzar el complement",
|
||||
'admin.addons.group.count': '{enabled} de {total} actius',
|
||||
'admin.addons.search': 'Cerca complements',
|
||||
'admin.addons.filter': 'Mostra',
|
||||
'admin.addons.filter.all': 'Tots',
|
||||
'admin.addons.filter.on': 'Actius',
|
||||
'admin.addons.filter.off': 'Inactius',
|
||||
'admin.addons.noMatch': 'Cap complement coincideix amb la cerca o el filtre.',
|
||||
'admin.addons.subOptions': '{count} opcions',
|
||||
'admin.addons.subOptions.one': '{count} opció',
|
||||
'admin.addons.subOptionsOn': '{count} actives',
|
||||
|
||||
@@ -657,6 +657,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Doplněk byl aktualizován',
|
||||
'admin.addons.toast.error': 'Aktualizace doplňku se nezdařila',
|
||||
'admin.addons.group.count': '{enabled} z {total} zapnuto',
|
||||
'admin.addons.search': 'Hledat doplňky',
|
||||
'admin.addons.filter': 'Zobrazit',
|
||||
'admin.addons.filter.all': 'Vše',
|
||||
'admin.addons.filter.on': 'Zapnuté',
|
||||
'admin.addons.filter.off': 'Vypnuté',
|
||||
'admin.addons.noMatch': 'Hledání ani filtru neodpovídá žádný doplněk.',
|
||||
'admin.addons.subOptions': '{count} možností',
|
||||
'admin.addons.subOptions.one': '{count} možnost',
|
||||
'admin.addons.subOptions.few': '{count} možnosti',
|
||||
|
||||
@@ -665,6 +665,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Addon aktualisiert',
|
||||
'admin.addons.toast.error': 'Addon konnte nicht aktualisiert werden',
|
||||
'admin.addons.group.count': '{enabled} von {total} aktiviert',
|
||||
'admin.addons.search': 'Addons durchsuchen',
|
||||
'admin.addons.filter': 'Anzeigen',
|
||||
'admin.addons.filter.all': 'Alle',
|
||||
'admin.addons.filter.on': 'An',
|
||||
'admin.addons.filter.off': 'Aus',
|
||||
'admin.addons.noMatch': 'Kein Addon passt zur Suche oder zum Filter.',
|
||||
'admin.addons.subOptions': '{count} Optionen',
|
||||
'admin.addons.subOptions.one': '{count} Option',
|
||||
'admin.addons.subOptionsOn': '{count} aktiv',
|
||||
|
||||
@@ -710,6 +710,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Addon updated',
|
||||
'admin.addons.toast.error': 'Failed to update addon',
|
||||
'admin.addons.group.count': '{enabled} of {total} enabled',
|
||||
'admin.addons.search': 'Search addons',
|
||||
'admin.addons.filter': 'Show',
|
||||
'admin.addons.filter.all': 'All',
|
||||
'admin.addons.filter.on': 'On',
|
||||
'admin.addons.filter.off': 'Off',
|
||||
'admin.addons.noMatch': 'No addon matches the search or the filter.',
|
||||
'admin.addons.subOptions': '{count} options',
|
||||
'admin.addons.subOptions.one': '{count} option',
|
||||
'admin.addons.subOptionsOn': '{count} on',
|
||||
|
||||
@@ -646,6 +646,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Complemento actualizado',
|
||||
'admin.addons.toast.error': 'No se pudo actualizar el complemento',
|
||||
'admin.addons.group.count': '{enabled} de {total} activados',
|
||||
'admin.addons.search': 'Buscar complementos',
|
||||
'admin.addons.filter': 'Mostrar',
|
||||
'admin.addons.filter.all': 'Todos',
|
||||
'admin.addons.filter.on': 'Activos',
|
||||
'admin.addons.filter.off': 'Inactivos',
|
||||
'admin.addons.noMatch': 'Ningún complemento coincide con la búsqueda o el filtro.',
|
||||
'admin.addons.subOptions': '{count} opciones',
|
||||
'admin.addons.subOptions.one': '{count} opción',
|
||||
'admin.addons.subOptionsOn': '{count} activas',
|
||||
|
||||
@@ -729,6 +729,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Lisamoodul uuendatud',
|
||||
'admin.addons.toast.error': 'Lisamooduli uuendamine ebaõnnestus',
|
||||
'admin.addons.group.count': 'Lubatud {enabled}/{total}',
|
||||
'admin.addons.search': 'Otsi lisasid',
|
||||
'admin.addons.filter': 'Näita',
|
||||
'admin.addons.filter.all': 'Kõik',
|
||||
'admin.addons.filter.on': 'Sees',
|
||||
'admin.addons.filter.off': 'Väljas',
|
||||
'admin.addons.noMatch': 'Otsingule ega filtrile ei vasta ükski lisa.',
|
||||
'admin.addons.subOptions': '{count} valikut',
|
||||
'admin.addons.subOptions.one': '{count} valik',
|
||||
'admin.addons.subOptionsOn': '{count} lubatud',
|
||||
|
||||
@@ -671,6 +671,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Extension mise à jour',
|
||||
'admin.addons.toast.error': "Échec de la mise à jour de l'extension",
|
||||
'admin.addons.group.count': '{enabled} sur {total} activés',
|
||||
'admin.addons.search': 'Rechercher des modules',
|
||||
'admin.addons.filter': 'Afficher',
|
||||
'admin.addons.filter.all': 'Tous',
|
||||
'admin.addons.filter.on': 'Activés',
|
||||
'admin.addons.filter.off': 'Désactivés',
|
||||
'admin.addons.noMatch': 'Aucun module ne correspond à la recherche ou au filtre.',
|
||||
'admin.addons.subOptions': '{count} options', // same-as-en
|
||||
'admin.addons.subOptions.one': '{count} option', // same-as-en
|
||||
'admin.addons.subOptionsOn': '{count} activées',
|
||||
|
||||
@@ -733,6 +733,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Το πρόσθετο ενημερώθηκε',
|
||||
'admin.addons.toast.error': 'Αποτυχία ενημέρωσης προσθέτου',
|
||||
'admin.addons.group.count': '{enabled} από {total} ενεργοποιημένα',
|
||||
'admin.addons.search': 'Αναζήτηση πρόσθετων',
|
||||
'admin.addons.filter': 'Εμφάνιση',
|
||||
'admin.addons.filter.all': 'Όλα',
|
||||
'admin.addons.filter.on': 'Ενεργά',
|
||||
'admin.addons.filter.off': 'Ανενεργά',
|
||||
'admin.addons.noMatch': 'Κανένα πρόσθετο δεν ταιριάζει με την αναζήτηση ή το φίλτρο.',
|
||||
'admin.addons.subOptions': '{count} επιλογές',
|
||||
'admin.addons.subOptions.one': '{count} επιλογή',
|
||||
'admin.addons.subOptionsOn': '{count} ενεργές',
|
||||
|
||||
@@ -671,6 +671,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Bővítmény frissítve',
|
||||
'admin.addons.toast.error': 'Nem sikerült frissíteni a bővítményt',
|
||||
'admin.addons.group.count': '{total} közül {enabled} bekapcsolva',
|
||||
'admin.addons.search': 'Bővítmények keresése',
|
||||
'admin.addons.filter': 'Megjelenítés',
|
||||
'admin.addons.filter.all': 'Mind',
|
||||
'admin.addons.filter.on': 'Be',
|
||||
'admin.addons.filter.off': 'Ki',
|
||||
'admin.addons.noMatch': 'Egy bővítmény sem felel meg a keresésnek vagy a szűrőnek.',
|
||||
'admin.addons.subOptions': '{count} beállítás',
|
||||
'admin.addons.subOptions.one': '{count} beállítás',
|
||||
'admin.addons.subOptionsOn': '{count} bekapcsolva',
|
||||
|
||||
@@ -711,6 +711,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Addon diperbarui',
|
||||
'admin.addons.toast.error': 'Gagal memperbarui addon',
|
||||
'admin.addons.group.count': '{enabled} dari {total} aktif',
|
||||
'admin.addons.search': 'Cari add-on',
|
||||
'admin.addons.filter': 'Tampilkan',
|
||||
'admin.addons.filter.all': 'Semua',
|
||||
'admin.addons.filter.on': 'Aktif',
|
||||
'admin.addons.filter.off': 'Nonaktif',
|
||||
'admin.addons.noMatch': 'Tidak ada add-on yang cocok dengan pencarian atau filter.',
|
||||
'admin.addons.subOptions': '{count} opsi',
|
||||
'admin.addons.subOptionsOn': '{count} aktif',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -665,6 +665,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Modulo aggiornato',
|
||||
'admin.addons.toast.error': 'Impossibile aggiornare il modulo',
|
||||
'admin.addons.group.count': '{enabled} di {total} attivi',
|
||||
'admin.addons.search': 'Cerca componenti aggiuntivi',
|
||||
'admin.addons.filter': 'Mostra',
|
||||
'admin.addons.filter.all': 'Tutti',
|
||||
'admin.addons.filter.on': 'Attivi',
|
||||
'admin.addons.filter.off': 'Disattivi',
|
||||
'admin.addons.noMatch': 'Nessun componente corrisponde alla ricerca o al filtro.',
|
||||
'admin.addons.subOptions': '{count} opzioni',
|
||||
'admin.addons.subOptions.one': '{count} opzione',
|
||||
'admin.addons.subOptionsOn': '{count} attive',
|
||||
|
||||
@@ -674,6 +674,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'アドオンを更新しました',
|
||||
'admin.addons.toast.error': 'アドオンの更新に失敗しました',
|
||||
'admin.addons.group.count': '{total} 件中 {enabled} 件が有効',
|
||||
'admin.addons.search': 'アドオンを検索',
|
||||
'admin.addons.filter': '表示',
|
||||
'admin.addons.filter.all': 'すべて',
|
||||
'admin.addons.filter.on': 'オン',
|
||||
'admin.addons.filter.off': 'オフ',
|
||||
'admin.addons.noMatch': '検索またはフィルターに一致するアドオンはありません。',
|
||||
'admin.addons.subOptions': '{count}件のオプション',
|
||||
'admin.addons.subOptionsOn': '{count}件が有効',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -673,6 +673,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': '애드온이 업데이트되었습니다',
|
||||
'admin.addons.toast.error': '애드온 업데이트 실패',
|
||||
'admin.addons.group.count': '{total}개 중 {enabled}개 활성화됨',
|
||||
'admin.addons.search': '애드온 검색',
|
||||
'admin.addons.filter': '표시',
|
||||
'admin.addons.filter.all': '전체',
|
||||
'admin.addons.filter.on': '켜짐',
|
||||
'admin.addons.filter.off': '꺼짐',
|
||||
'admin.addons.noMatch': '검색 또는 필터와 일치하는 애드온이 없습니다.',
|
||||
'admin.addons.subOptions': '옵션 {count}개',
|
||||
'admin.addons.subOptionsOn': '{count}개 켜짐',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -664,6 +664,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Add-on bijgewerkt',
|
||||
'admin.addons.toast.error': 'Add-on bijwerken mislukt',
|
||||
'admin.addons.group.count': '{enabled} van {total} ingeschakeld',
|
||||
'admin.addons.search': 'Add-ons zoeken',
|
||||
'admin.addons.filter': 'Tonen',
|
||||
'admin.addons.filter.all': 'Alle',
|
||||
'admin.addons.filter.on': 'Aan',
|
||||
'admin.addons.filter.off': 'Uit',
|
||||
'admin.addons.noMatch': 'Geen add-on past bij de zoekopdracht of het filter.',
|
||||
'admin.addons.subOptions': '{count} opties',
|
||||
'admin.addons.subOptions.one': '{count} optie',
|
||||
'admin.addons.subOptionsOn': '{count} aan',
|
||||
|
||||
@@ -680,6 +680,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Dodatek został zaktualizowany',
|
||||
'admin.addons.toast.error': 'Nie udało się zaktualizować dodatku',
|
||||
'admin.addons.group.count': '{enabled} z {total} włączonych',
|
||||
'admin.addons.search': 'Szukaj dodatków',
|
||||
'admin.addons.filter': 'Pokaż',
|
||||
'admin.addons.filter.all': 'Wszystkie',
|
||||
'admin.addons.filter.on': 'Włączone',
|
||||
'admin.addons.filter.off': 'Wyłączone',
|
||||
'admin.addons.noMatch': 'Żaden dodatek nie pasuje do wyszukiwania ani filtra.',
|
||||
'admin.addons.subOptions': '{count} opcji',
|
||||
'admin.addons.subOptions.one': '{count} opcja',
|
||||
'admin.addons.subOptions.few': '{count} opcje',
|
||||
|
||||
@@ -685,6 +685,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Дополнение обновлено',
|
||||
'admin.addons.toast.error': 'Не удалось обновить дополнение',
|
||||
'admin.addons.group.count': 'Включено {enabled} из {total}',
|
||||
'admin.addons.search': 'Поиск дополнений',
|
||||
'admin.addons.filter': 'Показать',
|
||||
'admin.addons.filter.all': 'Все',
|
||||
'admin.addons.filter.on': 'Включённые',
|
||||
'admin.addons.filter.off': 'Выключенные',
|
||||
'admin.addons.noMatch': 'Ни одно дополнение не подходит под поиск или фильтр.',
|
||||
'admin.addons.subOptions': '{count} параметра',
|
||||
'admin.addons.subOptions.one': '{count} параметр',
|
||||
'admin.addons.subOptions.few': '{count} параметра',
|
||||
|
||||
@@ -734,6 +734,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Doplnok aktualizovaný',
|
||||
'admin.addons.toast.error': 'Aktualizácia doplnku zlyhala',
|
||||
'admin.addons.group.count': '{enabled} z {total} zapnuté',
|
||||
'admin.addons.search': 'Hľadať doplnky',
|
||||
'admin.addons.filter': 'Zobraziť',
|
||||
'admin.addons.filter.all': 'Všetky',
|
||||
'admin.addons.filter.on': 'Zapnuté',
|
||||
'admin.addons.filter.off': 'Vypnuté',
|
||||
'admin.addons.noMatch': 'Vyhľadávaniu ani filtru nezodpovedá žiadny doplnok.',
|
||||
'admin.addons.subOptions': '{count} možností',
|
||||
'admin.addons.subOptions.one': '{count} možnosť',
|
||||
'admin.addons.subOptions.few': '{count} možnosti',
|
||||
|
||||
@@ -732,6 +732,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Tillägget har uppdaterats',
|
||||
'admin.addons.toast.error': 'Det gick inte att uppdatera tillägget',
|
||||
'admin.addons.group.count': '{enabled} av {total} aktiverade',
|
||||
'admin.addons.search': 'Sök tillägg',
|
||||
'admin.addons.filter': 'Visa',
|
||||
'admin.addons.filter.all': 'Alla',
|
||||
'admin.addons.filter.on': 'På',
|
||||
'admin.addons.filter.off': 'Av',
|
||||
'admin.addons.noMatch': 'Inget tillägg matchar sökningen eller filtret.',
|
||||
'admin.addons.subOptions': '{count} alternativ',
|
||||
'admin.addons.subOptions.one': '{count} alternativ',
|
||||
'admin.addons.subOptionsOn': '{count} aktiva',
|
||||
|
||||
@@ -631,6 +631,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'อัพเดตแอดออนแล้ว',
|
||||
'admin.addons.toast.error': 'ไม่สามารถอัปเดต addon',
|
||||
'admin.addons.group.count': 'เปิดใช้งาน {enabled} จาก {total} แล้ว',
|
||||
'admin.addons.search': 'ค้นหาส่วนเสริม',
|
||||
'admin.addons.filter': 'แสดง',
|
||||
'admin.addons.filter.all': 'ทั้งหมด',
|
||||
'admin.addons.filter.on': 'เปิด',
|
||||
'admin.addons.filter.off': 'ปิด',
|
||||
'admin.addons.noMatch': 'ไม่มีส่วนเสริมที่ตรงกับการค้นหาหรือตัวกรอง',
|
||||
'admin.addons.subOptions': '{count} ตัวเลือก',
|
||||
'admin.addons.subOptionsOn': 'เปิดอยู่ {count}',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -720,6 +720,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Eklenti güncellendi',
|
||||
'admin.addons.toast.error': 'Eklenti güncellenemedi',
|
||||
'admin.addons.group.count': '{total} eklentiden {enabled} tanesi etkin',
|
||||
'admin.addons.search': 'Eklentilerde ara',
|
||||
'admin.addons.filter': 'Göster',
|
||||
'admin.addons.filter.all': 'Tümü',
|
||||
'admin.addons.filter.on': 'Açık',
|
||||
'admin.addons.filter.off': 'Kapalı',
|
||||
'admin.addons.noMatch': 'Aramaya veya filtreye uyan eklenti yok.',
|
||||
'admin.addons.subOptions': '{count} seçenek',
|
||||
'admin.addons.subOptions.one': '{count} seçenek',
|
||||
'admin.addons.subOptionsOn': '{count} açık',
|
||||
|
||||
@@ -682,6 +682,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Доповнення оновлено',
|
||||
'admin.addons.toast.error': 'Не вдалося оновити доповнення',
|
||||
'admin.addons.group.count': 'Увімкнено {enabled} з {total}',
|
||||
'admin.addons.search': 'Пошук доповнень',
|
||||
'admin.addons.filter': 'Показати',
|
||||
'admin.addons.filter.all': 'Усі',
|
||||
'admin.addons.filter.on': 'Увімкнені',
|
||||
'admin.addons.filter.off': 'Вимкнені',
|
||||
'admin.addons.noMatch': 'Жодне доповнення не відповідає пошуку чи фільтру.',
|
||||
'admin.addons.subOptions': '{count} параметра',
|
||||
'admin.addons.subOptions.one': '{count} параметр',
|
||||
'admin.addons.subOptions.few': '{count} параметри',
|
||||
|
||||
@@ -724,6 +724,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': 'Đã cập nhật tiện ích bổ sung',
|
||||
'admin.addons.toast.error': 'Không thể cập nhật tiện ích bổ sung',
|
||||
'admin.addons.group.count': '{enabled} trên {total} đã bật',
|
||||
'admin.addons.search': 'Tìm tiện ích',
|
||||
'admin.addons.filter': 'Hiển thị',
|
||||
'admin.addons.filter.all': 'Tất cả',
|
||||
'admin.addons.filter.on': 'Bật',
|
||||
'admin.addons.filter.off': 'Tắt',
|
||||
'admin.addons.noMatch': 'Không có tiện ích nào khớp với tìm kiếm hoặc bộ lọc.',
|
||||
'admin.addons.subOptions': '{count} tùy chọn',
|
||||
'admin.addons.subOptionsOn': '{count} đang bật',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -645,6 +645,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': '擴充套件已更新',
|
||||
'admin.addons.toast.error': '更新擴充套件失敗',
|
||||
'admin.addons.group.count': '{total} 項中已啟用 {enabled} 項',
|
||||
'admin.addons.search': '搜尋外掛',
|
||||
'admin.addons.filter': '顯示',
|
||||
'admin.addons.filter.all': '全部',
|
||||
'admin.addons.filter.on': '已開啟',
|
||||
'admin.addons.filter.off': '已關閉',
|
||||
'admin.addons.noMatch': '沒有外掛符合搜尋或篩選條件。',
|
||||
'admin.addons.subOptions': '{count} 個選項',
|
||||
'admin.addons.subOptionsOn': '已開啟 {count} 個',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
@@ -596,6 +596,12 @@ const admin: TranslationStrings = {
|
||||
'admin.addons.toast.updated': '扩展已更新',
|
||||
'admin.addons.toast.error': '更新扩展失败',
|
||||
'admin.addons.group.count': '{total} 项中已启用 {enabled} 项',
|
||||
'admin.addons.search': '搜索插件',
|
||||
'admin.addons.filter': '显示',
|
||||
'admin.addons.filter.all': '全部',
|
||||
'admin.addons.filter.on': '已开启',
|
||||
'admin.addons.filter.off': '已关闭',
|
||||
'admin.addons.noMatch': '没有插件符合搜索或筛选条件。',
|
||||
'admin.addons.subOptions': '{count} 个选项',
|
||||
'admin.addons.subOptionsOn': '已开启 {count} 个',
|
||||
'admin.toursWanderer.title': 'wanderer',
|
||||
|
||||
Reference in New Issue
Block a user