refactor(themes): drop unreliable popularity/download stats from the store (#1050)

The install/download numbers came from jsDelivr CDN hits, which are
structurally unreliable: the stats API caps at the top 100 files, so most
themes report 0 and the figures jump from day to day. Showing them — and a
popularity bar and a most-popular sort built on them — only misled users.

Remove the stats meta box (popularity bar + download count), move the author
back under the theme name, and keep the reliable git-based last-changed date
inline. Sort is now Newest (default) or Alphabetical.
This commit is contained in:
Psychotoxical
2026-06-09 20:04:25 +02:00
committed by GitHub
parent c33d1e64c5
commit 316c99ba07
12 changed files with 37 additions and 125 deletions
-24
View File
@@ -1,24 +0,0 @@
/**
* Five-segment popularity bar, filled relative to the most-downloaded theme in
* the catalogue (so the leader reads full and the rest scale against it). With
* few downloads it sits near-empty and fills in organically as counts grow.
* Decorative — the exact download number sits next to it as the real value.
*/
export function PopularityBar({ value, max }: { value: number; max: number }) {
const filled = max > 0 ? Math.round((Math.max(0, value) / max) * 5) : 0;
return (
<span style={{ display: 'inline-flex', gap: 3 }} aria-hidden="true">
{Array.from({ length: 5 }, (_, i) => (
<span
key={i}
style={{
width: 16,
height: 5,
borderRadius: 2,
background: i < filled ? 'var(--accent)' : 'var(--bg-hover)',
}}
/>
))}
</span>
);
}
@@ -189,25 +189,19 @@ describe('ThemeStoreSection — pagination & refresh', () => {
expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument();
}); });
it('sorts by most popular, newest and name, and shows the download count', async () => { it('defaults to newest and sorts alphabetically', async () => {
const themes = [ const themes = [
mkTheme('a', 'Alpha', { installs: 10, updatedAt: '2026-06-01T00:00:00Z' }), mkTheme('a', 'Alpha', { updatedAt: '2026-06-01T00:00:00Z' }),
mkTheme('b', 'Bravo', { installs: 500, updatedAt: '2026-06-05T00:00:00Z' }), mkTheme('b', 'Bravo', { updatedAt: '2026-06-05T00:00:00Z' }),
mkTheme('c', 'Charlie', { installs: 0, updatedAt: '2026-06-03T00:00:00Z' }), mkTheme('c', 'Charlie', { updatedAt: '2026-06-03T00:00:00Z' }),
]; ];
fetchRegistryMock.mockResolvedValue({ registry: registryOf(themes), stale: false }); fetchRegistryMock.mockResolvedValue({ registry: registryOf(themes), stale: false });
const { container } = renderWithProviders(<ThemeStoreSection />); const { container } = renderWithProviders(<ThemeStoreSection />);
const user = userEvent.setup(); const user = userEvent.setup();
await screen.findByText('Bravo'); await screen.findByText('Bravo');
// Default sort is most-downloaded first: Bravo (500) > Alpha (10) > Charlie (0). // Default sort is newest first: Bravo (06-05) > Charlie (06-03) > Alpha (06-01).
expect(rowNames(container)).toEqual(['Bravo', 'Alpha', 'Charlie']); expect(rowNames(container)).toEqual(['Bravo', 'Charlie', 'Alpha']);
// The download count renders in the meta panel.
expect(screen.getByText('500')).toBeInTheDocument();
// Newest first: Bravo (06-05) > Charlie (06-03) > Alpha (06-01).
await selectSort(user, container, 'Newest');
await waitFor(() => expect(rowNames(container)).toEqual(['Bravo', 'Charlie', 'Alpha']));
// Alphabetical. // Alphabetical.
await selectSort(user, container, 'Alphabetical'); await selectSort(user, container, 'Alphabetical');
+13 -44
View File
@@ -5,7 +5,6 @@ import { open as openUrl } from '@tauri-apps/plugin-shell';
import CoverLightbox from '../CoverLightbox'; import CoverLightbox from '../CoverLightbox';
import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk'; import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk';
import { AnimatedThemeBadge } from './AnimatedThemeBadge'; import { AnimatedThemeBadge } from './AnimatedThemeBadge';
import { PopularityBar } from './PopularityBar';
import CustomSelect from '../CustomSelect'; import CustomSelect from '../CustomSelect';
import { formatRelativeTime } from '../../utils/format/relativeTime'; import { formatRelativeTime } from '../../utils/format/relativeTime';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
@@ -20,7 +19,7 @@ import { uninstallTheme } from '../../utils/themes/uninstallTheme';
import { isNewer } from '../../utils/componentHelpers/appUpdaterHelpers'; import { isNewer } from '../../utils/componentHelpers/appUpdaterHelpers';
type ModeFilter = 'all' | 'dark' | 'light'; type ModeFilter = 'all' | 'dark' | 'light';
type SortMode = 'popular' | 'newest' | 'name'; type SortMode = 'newest' | 'name';
const THEMES_REPO_URL = 'https://github.com/Psysonic/psysonic-themes'; const THEMES_REPO_URL = 'https://github.com/Psysonic/psysonic-themes';
@@ -61,7 +60,7 @@ export function ThemeStoreSection() {
const [stale, setStale] = useState(false); const [stale, setStale] = useState(false);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [mode, setMode] = useState<ModeFilter>('all'); const [mode, setMode] = useState<ModeFilter>('all');
const [sortMode, setSortMode] = useState<SortMode>('popular'); const [sortMode, setSortMode] = useState<SortMode>('newest');
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [busyId, setBusyId] = useState<string | null>(null); const [busyId, setBusyId] = useState<string | null>(null);
const [failedId, setFailedId] = useState<string | null>(null); const [failedId, setFailedId] = useState<string | null>(null);
@@ -98,14 +97,6 @@ export function ThemeStoreSection() {
return m; return m;
}, [installed]); }, [installed]);
// Scale the popularity bar against the most-downloaded theme across the whole
// catalogue (not the filtered view) so the bar means the same thing regardless
// of search/filter.
const maxInstalls = useMemo(
() => Math.max(1, ...(themes || []).map(th => th.installs ?? 0)),
[themes],
);
const filtered = useMemo(() => { const filtered = useMemo(() => {
if (!themes) return []; if (!themes) return [];
const q = query.trim().toLowerCase(); const q = query.trim().toLowerCase();
@@ -120,13 +111,10 @@ export function ThemeStoreSection() {
); );
}); });
// Name is the stable tie-breaker — keeps ordering deterministic when many // Name is the stable tie-breaker — keeps ordering deterministic when many
// themes share the same (often 0) download count. // themes share the same last-modified date.
const byName = (a: RegistryTheme, b: RegistryTheme) => a.name.localeCompare(b.name); const byName = (a: RegistryTheme, b: RegistryTheme) => a.name.localeCompare(b.name);
if (sortMode === 'name') return matched.sort(byName); if (sortMode === 'name') return matched.sort(byName);
if (sortMode === 'newest') { return matched.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || '') || byName(a, b));
return matched.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || '') || byName(a, b));
}
return matched.sort((a, b) => (b.installs ?? 0) - (a.installs ?? 0) || byName(a, b));
}, [themes, query, mode, sortMode]); }, [themes, query, mode, sortMode]);
// A changed filter can shrink the result set below the current page; reset to // A changed filter can shrink the result set below the current page; reset to
@@ -161,7 +149,6 @@ export function ThemeStoreSection() {
]; ];
const sortOptions = [ const sortOptions = [
{ value: 'popular', label: t('settings.themeStoreSortPopular') },
{ value: 'newest', label: t('settings.themeStoreSortNewest') }, { value: 'newest', label: t('settings.themeStoreSortNewest') },
{ value: 'name', label: t('settings.themeStoreSortName') }, { value: 'name', label: t('settings.themeStoreSortName') },
]; ];
@@ -335,11 +322,18 @@ export function ThemeStoreSection() {
)} )}
{animRisk && th.animated && <AnimatedThemeBadge variant="inline" />} {animRisk && th.animated && <AnimatedThemeBadge variant="inline" />}
</div> </div>
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>
{t('settings.themeStoreByAuthor', { author: th.author })}
</div>
<div style={{ fontSize: 12.5, color: 'var(--text-secondary)', lineHeight: 1.4, marginTop: 10 }}> <div style={{ fontSize: 12.5, color: 'var(--text-secondary)', lineHeight: 1.4, marginTop: 10 }}>
{th.description} {th.description}
</div> </div>
{/* Rating slot reserved — see Theme Store roadmap (deferred). */} {th.updatedAt && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 18 }}> <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 8 }}>
{t('settings.themeStoreLastChanged')}: {formatRelativeTime(th.updatedAt, i18n.language)}
</div>
)}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 12 }}>
{!isInstalled && ( {!isInstalled && (
<button <button
className="btn btn-primary" className="btn btn-primary"
@@ -385,31 +379,6 @@ export function ThemeStoreSection() {
)} )}
</div> </div>
</div> </div>
<div
className="theme-store-meta"
style={{ flexShrink: 0, width: 190, display: 'flex', flexDirection: 'column', gap: 6, padding: '10px 12px', background: 'var(--bg-deep, var(--bg-elevated))', border: '1px solid var(--border)', borderRadius: 'var(--radius-md, 10px)' }}
>
<div>
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{t('settings.themeStoreAuthor')}</div>
<div style={{ fontSize: 12.5, color: 'var(--text-secondary)', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{th.author}</div>
</div>
<div style={{ marginTop: 4, display: 'flex', flexDirection: 'column', gap: 6 }}>
<div>
<div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 4 }}>{t('settings.themeStorePopularity')}</div>
<PopularityBar value={th.installs ?? 0} max={maxInstalls} />
</div>
<div>
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{t('settings.themeStoreDownloads')}</div>
<div style={{ fontSize: 13, color: 'var(--text-secondary)', fontWeight: 600 }}>{(th.installs ?? 0).toLocaleString(i18n.language)}</div>
</div>
{th.updatedAt && (
<div>
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{t('settings.themeStoreLastChanged')}</div>
<div style={{ fontSize: 12.5, color: 'var(--text-secondary)' }}>{formatRelativeTime(th.updatedAt, i18n.language)}</div>
</div>
)}
</div>
</div>
</div> </div>
); );
})} })}
+2 -5
View File
@@ -356,7 +356,7 @@ export const settings = {
themeStoreSubmitText: 'Eigenes Theme gebaut? Teil es mit der Community — mehr Infos im Themes-Repository.', themeStoreSubmitText: 'Eigenes Theme gebaut? Teil es mit der Community — mehr Infos im Themes-Repository.',
themeStoreSubmitLink: 'Themes-Repository öffnen', themeStoreSubmitLink: 'Themes-Repository öffnen',
themeStoreNetworkNotice: 'Der Theme-Store lädt Katalog und Vorschauen von externen Diensten (jsDelivr-CDN und GitHub). Es werden keine persönlichen Daten gesendet.', themeStoreNetworkNotice: 'Der Theme-Store lädt Katalog und Vorschauen von externen Diensten (jsDelivr-CDN und GitHub). Es werden keine persönlichen Daten gesendet.',
themeStoreStatsNotice: 'Download- und „Zuletzt geändert"-Statistiken werden einmal täglich aktualisiert (gegen 04:17 UTC).', themeStoreStatsNotice: 'Die „Zuletzt geändert“-Daten werden einmal täglich aktualisiert (gegen 04:17 UTC).',
themeStoreSearchPlaceholder: 'Themes suchen…', themeStoreSearchPlaceholder: 'Themes suchen…',
themeStoreFilterMode: 'Nach Modus filtern', themeStoreFilterMode: 'Nach Modus filtern',
themeStoreModeAll: 'Alle', themeStoreModeAll: 'Alle',
@@ -370,12 +370,9 @@ export const settings = {
themeStoreEmpty: 'Keine Themes passen zu deinen Filtern.', themeStoreEmpty: 'Keine Themes passen zu deinen Filtern.',
themeStoreActive: 'Aktiv', themeStoreActive: 'Aktiv',
themeStoreEnlarge: 'Vorschau vergrößern', themeStoreEnlarge: 'Vorschau vergrößern',
themeStoreSortPopular: 'Beliebteste',
themeStoreSortNewest: 'Neueste', themeStoreSortNewest: 'Neueste',
themeStoreSortName: 'Alphabetisch', themeStoreSortName: 'Alphabetisch',
themeStoreAuthor: 'Autor', themeStoreByAuthor: 'von {{author}}',
themeStorePopularity: 'Beliebtheit',
themeStoreDownloads: 'Downloads gesamt',
themeStoreLastChanged: 'Zuletzt geändert', themeStoreLastChanged: 'Zuletzt geändert',
themeMigrationNoticeTitle: 'Theme in den Store gezogen', themeMigrationNoticeTitle: 'Theme in den Store gezogen',
themeMigrationNoticeBody: '{{themes}} ist nicht mehr in der App enthalten. Du kannst es im Theme-Store neu installieren (Einstellungen → Themes) oder ein anderes Theme wählen.', themeMigrationNoticeBody: '{{themes}} ist nicht mehr in der App enthalten. Du kannst es im Theme-Store neu installieren (Einstellungen → Themes) oder ein anderes Theme wählen.',
+2 -5
View File
@@ -400,7 +400,7 @@ export const settings = {
themeStoreSubmitText: 'Made your own theme? Share it with the community — more info in the themes repository.', themeStoreSubmitText: 'Made your own theme? Share it with the community — more info in the themes repository.',
themeStoreSubmitLink: 'Open the themes repository', themeStoreSubmitLink: 'Open the themes repository',
themeStoreNetworkNotice: 'The Theme Store loads its catalogue and previews from external services (the jsDelivr CDN and GitHub). No personal data is sent.', themeStoreNetworkNotice: 'The Theme Store loads its catalogue and previews from external services (the jsDelivr CDN and GitHub). No personal data is sent.',
themeStoreStatsNotice: 'Download and last-changed stats refresh once a day (around 04:17 UTC).', themeStoreStatsNotice: 'Last-changed dates refresh once a day (around 04:17 UTC).',
themeStoreSearchPlaceholder: 'Search themes…', themeStoreSearchPlaceholder: 'Search themes…',
themeStoreFilterMode: 'Filter by mode', themeStoreFilterMode: 'Filter by mode',
themeStoreModeAll: 'All', themeStoreModeAll: 'All',
@@ -414,12 +414,9 @@ export const settings = {
themeStoreEmpty: 'No themes match your filters.', themeStoreEmpty: 'No themes match your filters.',
themeStoreActive: 'Active', themeStoreActive: 'Active',
themeStoreEnlarge: 'Enlarge preview', themeStoreEnlarge: 'Enlarge preview',
themeStoreSortPopular: 'Most popular',
themeStoreSortNewest: 'Newest', themeStoreSortNewest: 'Newest',
themeStoreSortName: 'Alphabetical', themeStoreSortName: 'Alphabetical',
themeStoreAuthor: 'Author', themeStoreByAuthor: 'by {{author}}',
themeStorePopularity: 'Popularity',
themeStoreDownloads: 'Total downloads',
themeStoreLastChanged: 'Last changed', themeStoreLastChanged: 'Last changed',
themeMigrationNoticeTitle: 'Theme moved to the Store', themeMigrationNoticeTitle: 'Theme moved to the Store',
themeMigrationNoticeBody: '{{themes}} is no longer bundled with the app. You can reinstall it from the Theme Store (Settings → Themes), or pick another theme.', themeMigrationNoticeBody: '{{themes}} is no longer bundled with the app. You can reinstall it from the Theme Store (Settings → Themes), or pick another theme.',
+2 -5
View File
@@ -354,7 +354,7 @@ export const settings = {
themeStoreSubmitText: '¿Has creado tu propio tema? Compártelo con la comunidad — más información en el repositorio de temas.', themeStoreSubmitText: '¿Has creado tu propio tema? Compártelo con la comunidad — más información en el repositorio de temas.',
themeStoreSubmitLink: 'Abrir el repositorio de temas', themeStoreSubmitLink: 'Abrir el repositorio de temas',
themeStoreNetworkNotice: 'La tienda de temas carga el catálogo y las vistas previas desde servicios externos (la CDN de jsDelivr y GitHub). No se envían datos personales.', themeStoreNetworkNotice: 'La tienda de temas carga el catálogo y las vistas previas desde servicios externos (la CDN de jsDelivr y GitHub). No se envían datos personales.',
themeStoreStatsNotice: 'Las estadísticas de descargas y última modificación se actualizan una vez al día (sobre las 04:17 UTC).', themeStoreStatsNotice: 'Las fechas de última modificación se actualizan una vez al día (sobre las 04:17 UTC).',
themeStoreSearchPlaceholder: 'Buscar temas…', themeStoreSearchPlaceholder: 'Buscar temas…',
themeStoreFilterMode: 'Filtrar por modo', themeStoreFilterMode: 'Filtrar por modo',
themeStoreModeAll: 'Todos', themeStoreModeAll: 'Todos',
@@ -368,12 +368,9 @@ export const settings = {
themeStoreEmpty: 'Ningún tema coincide con tus filtros.', themeStoreEmpty: 'Ningún tema coincide con tus filtros.',
themeStoreActive: 'Activo', themeStoreActive: 'Activo',
themeStoreEnlarge: 'Ampliar vista previa', themeStoreEnlarge: 'Ampliar vista previa',
themeStoreSortPopular: 'Más populares',
themeStoreSortNewest: 'Más recientes', themeStoreSortNewest: 'Más recientes',
themeStoreSortName: 'Alfabético', themeStoreSortName: 'Alfabético',
themeStoreAuthor: 'Autor', themeStoreByAuthor: 'por {{author}}',
themeStorePopularity: 'Popularidad',
themeStoreDownloads: 'Descargas totales',
themeStoreLastChanged: 'Última modificación', themeStoreLastChanged: 'Última modificación',
themeMigrationNoticeTitle: 'Tema movido a la tienda', themeMigrationNoticeTitle: 'Tema movido a la tienda',
themeMigrationNoticeBody: '{{themes}} ya no viene incluido en la app. Puedes reinstalarlo desde la Tienda de Temas (Ajustes → Temas) o elegir otro tema.', themeMigrationNoticeBody: '{{themes}} ya no viene incluido en la app. Puedes reinstalarlo desde la Tienda de Temas (Ajustes → Temas) o elegir otro tema.',
+2 -5
View File
@@ -352,7 +352,7 @@ export const settings = {
themeStoreSubmitText: "Vous avez créé votre propre thème ? Partagez-le avec la communauté — plus d'infos dans le dépôt de thèmes.", themeStoreSubmitText: "Vous avez créé votre propre thème ? Partagez-le avec la communauté — plus d'infos dans le dépôt de thèmes.",
themeStoreSubmitLink: 'Ouvrir le dépôt de thèmes', themeStoreSubmitLink: 'Ouvrir le dépôt de thèmes',
themeStoreNetworkNotice: 'Le Theme Store charge son catalogue et ses aperçus depuis des services externes (le CDN jsDelivr et GitHub). Aucune donnée personnelle nest envoyée.', themeStoreNetworkNotice: 'Le Theme Store charge son catalogue et ses aperçus depuis des services externes (le CDN jsDelivr et GitHub). Aucune donnée personnelle nest envoyée.',
themeStoreStatsNotice: 'Les statistiques de téléchargements et de dernière modification sont actualisées une fois par jour (vers 04h17 UTC).', themeStoreStatsNotice: 'Les dates de dernière modification sont actualisées une fois par jour (vers 04h17 UTC).',
themeStoreSearchPlaceholder: 'Rechercher des thèmes…', themeStoreSearchPlaceholder: 'Rechercher des thèmes…',
themeStoreFilterMode: 'Filtrer par mode', themeStoreFilterMode: 'Filtrer par mode',
themeStoreModeAll: 'Tous', themeStoreModeAll: 'Tous',
@@ -366,12 +366,9 @@ export const settings = {
themeStoreEmpty: 'Aucun thème ne correspond à vos filtres.', themeStoreEmpty: 'Aucun thème ne correspond à vos filtres.',
themeStoreActive: 'Actif', themeStoreActive: 'Actif',
themeStoreEnlarge: "Agrandir l'aperçu", themeStoreEnlarge: "Agrandir l'aperçu",
themeStoreSortPopular: 'Les plus populaires',
themeStoreSortNewest: 'Plus récents', themeStoreSortNewest: 'Plus récents',
themeStoreSortName: 'Alphabétique', themeStoreSortName: 'Alphabétique',
themeStoreAuthor: 'Auteur', themeStoreByAuthor: 'par {{author}}',
themeStorePopularity: 'Popularité',
themeStoreDownloads: 'Téléchargements totaux',
themeStoreLastChanged: 'Dernière modification', themeStoreLastChanged: 'Dernière modification',
themeMigrationNoticeTitle: 'Thème déplacé vers la boutique', themeMigrationNoticeTitle: 'Thème déplacé vers la boutique',
themeMigrationNoticeBody: "{{themes}} n'est plus inclus dans l'application. Vous pouvez le réinstaller depuis la boutique de thèmes (Paramètres → Thèmes) ou choisir un autre thème.", themeMigrationNoticeBody: "{{themes}} n'est plus inclus dans l'application. Vous pouvez le réinstaller depuis la boutique de thèmes (Paramètres → Thèmes) ou choisir un autre thème.",
+2 -5
View File
@@ -355,7 +355,7 @@ export const settings = {
themeStoreSubmitText: 'Laget ditt eget tema? Del det med fellesskapet — mer info i tema-repoet.', themeStoreSubmitText: 'Laget ditt eget tema? Del det med fellesskapet — mer info i tema-repoet.',
themeStoreSubmitLink: 'Åpne tema-repoet', themeStoreSubmitLink: 'Åpne tema-repoet',
themeStoreNetworkNotice: 'Theme Store laster katalogen og forhåndsvisninger fra eksterne tjenester (jsDelivr-CDN og GitHub). Ingen personopplysninger sendes.', themeStoreNetworkNotice: 'Theme Store laster katalogen og forhåndsvisninger fra eksterne tjenester (jsDelivr-CDN og GitHub). Ingen personopplysninger sendes.',
themeStoreStatsNotice: 'Statistikk for nedlastinger og sist endret oppdateres én gang om dagen (rundt 04:17 UTC).', themeStoreStatsNotice: 'Sist endret-datoene oppdateres én gang om dagen (rundt 04:17 UTC).',
themeStoreSearchPlaceholder: 'Søk i temaer…', themeStoreSearchPlaceholder: 'Søk i temaer…',
themeStoreFilterMode: 'Filtrer etter modus', themeStoreFilterMode: 'Filtrer etter modus',
themeStoreModeAll: 'Alle', themeStoreModeAll: 'Alle',
@@ -369,12 +369,9 @@ export const settings = {
themeStoreEmpty: 'Ingen temaer samsvarer med filtrene dine.', themeStoreEmpty: 'Ingen temaer samsvarer med filtrene dine.',
themeStoreActive: 'Aktiv', themeStoreActive: 'Aktiv',
themeStoreEnlarge: 'Forstørr forhåndsvisning', themeStoreEnlarge: 'Forstørr forhåndsvisning',
themeStoreSortPopular: 'Mest populære',
themeStoreSortNewest: 'Nyeste', themeStoreSortNewest: 'Nyeste',
themeStoreSortName: 'Alfabetisk', themeStoreSortName: 'Alfabetisk',
themeStoreAuthor: 'Forfatter', themeStoreByAuthor: 'av {{author}}',
themeStorePopularity: 'Popularitet',
themeStoreDownloads: 'Totalt nedlastinger',
themeStoreLastChanged: 'Sist endret', themeStoreLastChanged: 'Sist endret',
themeMigrationNoticeTitle: 'Temaet er flyttet til butikken', themeMigrationNoticeTitle: 'Temaet er flyttet til butikken',
themeMigrationNoticeBody: '{{themes}} følger ikke lenger med appen. Du kan installere det på nytt fra temabutikken (Innstillinger → Temaer), eller velge et annet tema.', themeMigrationNoticeBody: '{{themes}} følger ikke lenger med appen. Du kan installere det på nytt fra temabutikken (Innstillinger → Temaer), eller velge et annet tema.',
+2 -5
View File
@@ -352,7 +352,7 @@ export const settings = {
themeStoreSubmitText: 'Eigen thema gemaakt? Deel het met de community — meer info in de themarepository.', themeStoreSubmitText: 'Eigen thema gemaakt? Deel het met de community — meer info in de themarepository.',
themeStoreSubmitLink: 'Themarepository openen', themeStoreSubmitLink: 'Themarepository openen',
themeStoreNetworkNotice: 'De Theme Store laadt de catalogus en previews van externe diensten (de jsDelivr-CDN en GitHub). Er worden geen persoonlijke gegevens verzonden.', themeStoreNetworkNotice: 'De Theme Store laadt de catalogus en previews van externe diensten (de jsDelivr-CDN en GitHub). Er worden geen persoonlijke gegevens verzonden.',
themeStoreStatsNotice: 'Download- en laatst-gewijzigd-statistieken worden eenmaal per dag bijgewerkt (rond 04:17 UTC).', themeStoreStatsNotice: 'De laatst-gewijzigd-datums worden eenmaal per dag bijgewerkt (rond 04:17 UTC).',
themeStoreSearchPlaceholder: "Thema's zoeken…", themeStoreSearchPlaceholder: "Thema's zoeken…",
themeStoreFilterMode: 'Filteren op modus', themeStoreFilterMode: 'Filteren op modus',
themeStoreModeAll: 'Alle', themeStoreModeAll: 'Alle',
@@ -366,12 +366,9 @@ export const settings = {
themeStoreEmpty: "Geen thema's komen overeen met je filters.", themeStoreEmpty: "Geen thema's komen overeen met je filters.",
themeStoreActive: 'Actief', themeStoreActive: 'Actief',
themeStoreEnlarge: 'Voorbeeld vergroten', themeStoreEnlarge: 'Voorbeeld vergroten',
themeStoreSortPopular: 'Populairste',
themeStoreSortNewest: 'Nieuwste', themeStoreSortNewest: 'Nieuwste',
themeStoreSortName: 'Alfabetisch', themeStoreSortName: 'Alfabetisch',
themeStoreAuthor: 'Auteur', themeStoreByAuthor: 'door {{author}}',
themeStorePopularity: 'Populariteit',
themeStoreDownloads: 'Totaal downloads',
themeStoreLastChanged: 'Laatst gewijzigd', themeStoreLastChanged: 'Laatst gewijzigd',
themeMigrationNoticeTitle: 'Thema verplaatst naar de store', themeMigrationNoticeTitle: 'Thema verplaatst naar de store',
themeMigrationNoticeBody: '{{themes}} wordt niet meer met de app meegeleverd. Je kunt het opnieuw installeren via de Theme Store (Instellingen → Thema\'s) of een ander thema kiezen.', themeMigrationNoticeBody: '{{themes}} wordt niet meer met de app meegeleverd. Je kunt het opnieuw installeren via de Theme Store (Instellingen → Thema\'s) of een ander thema kiezen.',
+2 -5
View File
@@ -358,7 +358,7 @@ export const settings = {
themeStoreSubmitText: 'Ți-ai creat propria temă? Împărtășește-o cu comunitatea — mai multe informații în depozitul de teme.', themeStoreSubmitText: 'Ți-ai creat propria temă? Împărtășește-o cu comunitatea — mai multe informații în depozitul de teme.',
themeStoreSubmitLink: 'Deschide depozitul de teme', themeStoreSubmitLink: 'Deschide depozitul de teme',
themeStoreNetworkNotice: 'Theme Store încarcă catalogul și previzualizările de la servicii externe (CDN-ul jsDelivr și GitHub). Nu se trimit date personale.', themeStoreNetworkNotice: 'Theme Store încarcă catalogul și previzualizările de la servicii externe (CDN-ul jsDelivr și GitHub). Nu se trimit date personale.',
themeStoreStatsNotice: 'Statisticile de descărcări și ultima modificare se actualizează o dată pe zi (în jurul orei 04:17 UTC).', themeStoreStatsNotice: 'Datele privind ultima modificare se actualizează o dată pe zi (în jurul orei 04:17 UTC).',
themeStoreSearchPlaceholder: 'Caută teme…', themeStoreSearchPlaceholder: 'Caută teme…',
themeStoreFilterMode: 'Filtrează după mod', themeStoreFilterMode: 'Filtrează după mod',
themeStoreModeAll: 'Toate', themeStoreModeAll: 'Toate',
@@ -372,12 +372,9 @@ export const settings = {
themeStoreEmpty: 'Nicio temă nu corespunde filtrelor tale.', themeStoreEmpty: 'Nicio temă nu corespunde filtrelor tale.',
themeStoreActive: 'Activă', themeStoreActive: 'Activă',
themeStoreEnlarge: 'Mărește previzualizarea', themeStoreEnlarge: 'Mărește previzualizarea',
themeStoreSortPopular: 'Cele mai populare',
themeStoreSortNewest: 'Cele mai noi', themeStoreSortNewest: 'Cele mai noi',
themeStoreSortName: 'Alfabetic', themeStoreSortName: 'Alfabetic',
themeStoreAuthor: 'Autor', themeStoreByAuthor: 'de {{author}}',
themeStorePopularity: 'Popularitate',
themeStoreDownloads: 'Descărcări totale',
themeStoreLastChanged: 'Ultima modificare', themeStoreLastChanged: 'Ultima modificare',
themeMigrationNoticeTitle: 'Tema a fost mutată în magazin', themeMigrationNoticeTitle: 'Tema a fost mutată în magazin',
themeMigrationNoticeBody: '{{themes}} nu mai este inclusă în aplicație. O poți reinstala din Magazinul de Teme (Setări → Teme) sau poți alege altă temă.', themeMigrationNoticeBody: '{{themes}} nu mai este inclusă în aplicație. O poți reinstala din Magazinul de Teme (Setări → Teme) sau poți alege altă temă.',
+2 -5
View File
@@ -411,7 +411,7 @@ export const settings = {
themeStoreSubmitText: 'Создали свою тему? Поделитесь с сообществом — подробности в репозитории тем.', themeStoreSubmitText: 'Создали свою тему? Поделитесь с сообществом — подробности в репозитории тем.',
themeStoreSubmitLink: 'Открыть репозиторий тем', themeStoreSubmitLink: 'Открыть репозиторий тем',
themeStoreNetworkNotice: 'Магазин тем загружает каталог и превью с внешних сервисов (CDN jsDelivr и GitHub). Личные данные не отправляются.', themeStoreNetworkNotice: 'Магазин тем загружает каталог и превью с внешних сервисов (CDN jsDelivr и GitHub). Личные данные не отправляются.',
themeStoreStatsNotice: 'Статистика загрузок и даты изменения обновляются раз в день (около 04:17 UTC).', themeStoreStatsNotice: 'Даты последнего изменения обновляются раз в день (около 04:17 UTC).',
themeStoreSearchPlaceholder: 'Поиск тем…', themeStoreSearchPlaceholder: 'Поиск тем…',
themeStoreFilterMode: 'Фильтр по режиму', themeStoreFilterMode: 'Фильтр по режиму',
themeStoreModeAll: 'Все', themeStoreModeAll: 'Все',
@@ -425,12 +425,9 @@ export const settings = {
themeStoreEmpty: 'Нет тем, соответствующих фильтрам.', themeStoreEmpty: 'Нет тем, соответствующих фильтрам.',
themeStoreActive: 'Активна', themeStoreActive: 'Активна',
themeStoreEnlarge: 'Увеличить превью', themeStoreEnlarge: 'Увеличить превью',
themeStoreSortPopular: 'Популярные',
themeStoreSortNewest: 'Новые', themeStoreSortNewest: 'Новые',
themeStoreSortName: 'По алфавиту', themeStoreSortName: 'По алфавиту',
themeStoreAuthor: 'Автор', themeStoreByAuthor: 'от {{author}}',
themeStorePopularity: 'Популярность',
themeStoreDownloads: 'Всего загрузок',
themeStoreLastChanged: 'Изменён', themeStoreLastChanged: 'Изменён',
themeMigrationNoticeTitle: 'Тема перенесена в магазин', themeMigrationNoticeTitle: 'Тема перенесена в магазин',
themeMigrationNoticeBody: '{{themes}} больше не входит в приложение. Вы можете переустановить её из магазина тем (Настройки → Темы) или выбрать другую тему.', themeMigrationNoticeBody: '{{themes}} больше не входит в приложение. Вы можете переустановить её из магазина тем (Настройки → Темы) или выбрать другую тему.',
+2 -5
View File
@@ -351,7 +351,7 @@ export const settings = {
themeStoreSubmitText: '做了自己的主题?与社区分享——更多信息见主题仓库。', themeStoreSubmitText: '做了自己的主题?与社区分享——更多信息见主题仓库。',
themeStoreSubmitLink: '打开主题仓库', themeStoreSubmitLink: '打开主题仓库',
themeStoreNetworkNotice: '主题商店从外部服务(jsDelivr CDN 和 GitHub)加载目录和预览。不会发送任何个人数据。', themeStoreNetworkNotice: '主题商店从外部服务(jsDelivr CDN 和 GitHub)加载目录和预览。不会发送任何个人数据。',
themeStoreStatsNotice: '下载量和最后更新时间等统计数据每天更新一次(约 04:17 UTC)。', themeStoreStatsNotice: '最后更新时间每天更新一次(约 04:17 UTC)。',
themeStoreSearchPlaceholder: '搜索主题…', themeStoreSearchPlaceholder: '搜索主题…',
themeStoreFilterMode: '按模式筛选', themeStoreFilterMode: '按模式筛选',
themeStoreModeAll: '全部', themeStoreModeAll: '全部',
@@ -365,12 +365,9 @@ export const settings = {
themeStoreEmpty: '没有符合筛选条件的主题。', themeStoreEmpty: '没有符合筛选条件的主题。',
themeStoreActive: '使用中', themeStoreActive: '使用中',
themeStoreEnlarge: '放大预览', themeStoreEnlarge: '放大预览',
themeStoreSortPopular: '最受欢迎',
themeStoreSortNewest: '最新', themeStoreSortNewest: '最新',
themeStoreSortName: '按字母', themeStoreSortName: '按字母',
themeStoreAuthor: '作者', themeStoreByAuthor: '作者{{author}}',
themeStorePopularity: '热门度',
themeStoreDownloads: '总下载量',
themeStoreLastChanged: '最后更新', themeStoreLastChanged: '最后更新',
themeMigrationNoticeTitle: '主题已移至商店', themeMigrationNoticeTitle: '主题已移至商店',
themeMigrationNoticeBody: '{{themes}} 不再内置于应用中。你可以在主题商店(设置 → 主题)重新安装,或选择其他主题。', themeMigrationNoticeBody: '{{themes}} 不再内置于应用中。你可以在主题商店(设置 → 主题)重新安装,或选择其他主题。',