mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
feat(themes): import a theme from a local .zip (#1012)
* feat(themes): validate and extract locally imported theme packages Add the backend + validation half of local theme import: users will be able to load a theme packaged as a .zip (manifest.json + theme.css). - `import_theme_zip` Tauri command unpacks only manifest.json + theme.css from the archive, outside the webview, with an archive-size cap, per-entry uncompressed caps, and path-traversal rejection. - `validateThemePackage` runs the full theme-store contract in-app: the manifest schema (field patterns copied from the repo schema), the CSS token whitelist with all core tokens required, color-scheme matching the declared mode, data-URI restricted to the arrow token, and a guard against ids that collide with built-in themes. The existing `validateThemeCss` containment guard is reused and runs again at injection time. The contract token list is a byte-identical copy of the themes repo's allowed-tokens.json. Covered by validateThemePackage tests for every rejection class. * feat(themes): add the Import a theme section to the Themes tab A dedicated section between the theme scheduler and the Theme Store lets users import a theme from a local .zip. After the package validates, a confirmation dialog names the theme and its author before it is installed. A rejected import shows a plain-language explanation aimed at end users; the raw contract diagnostics (token names, missing fields) are tucked into a collapsible "Technical details" block for theme authors. Fully localised across all nine languages. * docs(themes): changelog + credits for local theme import Fold the local .zip import (and the store pagination / refresh-scroll polish) into the still-unreleased Theme Store entry rather than a new section, and extend the credits line. PRs #1011 and #1012.
This commit is contained in:
@@ -354,6 +354,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Vorherige Seite',
|
||||
themeStorePageNext: 'Nächste Seite',
|
||||
themeStorePageStatus: 'Seite {{page}} von {{total}}',
|
||||
themeImportTitle: 'Theme importieren',
|
||||
themeImportButton: 'Theme importieren…',
|
||||
themeImporting: 'Importiere…',
|
||||
themeImportHint: 'Lädt ein als .zip verpacktes Theme (manifest.json + theme.css).',
|
||||
themeImportSuccess: '„{{name}}“ importiert.',
|
||||
themeImportErrorTitle: 'Dieses Theme konnte nicht importiert werden',
|
||||
themeImportErrorBody: 'Es entspricht nicht dem Psysonic-Theme-Format. Vielleicht wurde es für eine andere Version erstellt oder die Datei ist unvollständig — prüfe, ob du die richtige .zip gewählt hast, oder frag den Autor des Themes.',
|
||||
themeImportErrorDetails: 'Technische Details',
|
||||
themeImportConfirmTitle: 'Theme installieren?',
|
||||
themeImportConfirmBody: '„{{name}}“ von {{author}} installieren?',
|
||||
tabLibrary: 'Bibliothek',
|
||||
tabServers: 'Server',
|
||||
tabLyrics: 'Songtexte',
|
||||
|
||||
@@ -421,6 +421,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Previous page',
|
||||
themeStorePageNext: 'Next page',
|
||||
themeStorePageStatus: 'Page {{page}} of {{total}}',
|
||||
themeImportTitle: 'Import a theme',
|
||||
themeImportButton: 'Import theme…',
|
||||
themeImporting: 'Importing…',
|
||||
themeImportHint: 'Load a theme packaged as a .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '"{{name}}" imported.',
|
||||
themeImportErrorTitle: 'This theme could not be imported',
|
||||
themeImportErrorBody: "It doesn't match the Psysonic theme format. It may have been made for a different version, or the file is incomplete — check that you picked the right .zip, or ask the theme's author.",
|
||||
themeImportErrorDetails: 'Technical details',
|
||||
themeImportConfirmTitle: 'Install theme?',
|
||||
themeImportConfirmBody: 'Install "{{name}}" by {{author}}?',
|
||||
tabLibrary: 'Library',
|
||||
tabServers: 'Servers',
|
||||
tabLyrics: 'Lyrics',
|
||||
|
||||
@@ -352,6 +352,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Página anterior',
|
||||
themeStorePageNext: 'Página siguiente',
|
||||
themeStorePageStatus: 'Página {{page}} de {{total}}',
|
||||
themeImportTitle: 'Importar un tema',
|
||||
themeImportButton: 'Importar tema…',
|
||||
themeImporting: 'Importando…',
|
||||
themeImportHint: 'Carga un tema empaquetado como .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '«{{name}}» importado.',
|
||||
themeImportErrorTitle: 'No se pudo importar este tema',
|
||||
themeImportErrorBody: 'No coincide con el formato de temas de Psysonic. Puede que se haya creado para otra versión o que el archivo esté incompleto: comprueba que elegiste el .zip correcto o pregunta al autor del tema.',
|
||||
themeImportErrorDetails: 'Detalles técnicos',
|
||||
themeImportConfirmTitle: '¿Instalar tema?',
|
||||
themeImportConfirmBody: '¿Instalar «{{name}}» de {{author}}?',
|
||||
tabLibrary: 'Biblioteca',
|
||||
tabServers: 'Servidores',
|
||||
tabLyrics: 'Letras',
|
||||
|
||||
@@ -350,6 +350,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Page précédente',
|
||||
themeStorePageNext: 'Page suivante',
|
||||
themeStorePageStatus: 'Page {{page}} sur {{total}}',
|
||||
themeImportTitle: 'Importer un thème',
|
||||
themeImportButton: 'Importer un thème…',
|
||||
themeImporting: 'Importation…',
|
||||
themeImportHint: 'Charge un thème empaqueté en .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '« {{name}} » importé.',
|
||||
themeImportErrorTitle: "Ce thème n'a pas pu être importé",
|
||||
themeImportErrorBody: "Il ne correspond pas au format de thème Psysonic. Il a peut-être été conçu pour une autre version, ou le fichier est incomplet — vérifiez que vous avez choisi le bon .zip, ou contactez l'auteur du thème.",
|
||||
themeImportErrorDetails: 'Détails techniques',
|
||||
themeImportConfirmTitle: 'Installer le thème ?',
|
||||
themeImportConfirmBody: 'Installer « {{name}} » de {{author}} ?',
|
||||
tabLibrary: 'Bibliothèque',
|
||||
tabServers: 'Serveurs',
|
||||
tabLyrics: 'Paroles',
|
||||
|
||||
@@ -353,6 +353,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Forrige side',
|
||||
themeStorePageNext: 'Neste side',
|
||||
themeStorePageStatus: 'Side {{page}} av {{total}}',
|
||||
themeImportTitle: 'Importer et tema',
|
||||
themeImportButton: 'Importer tema…',
|
||||
themeImporting: 'Importerer…',
|
||||
themeImportHint: 'Laster inn et tema pakket som .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '«{{name}}» importert.',
|
||||
themeImportErrorTitle: 'Dette temaet kunne ikke importeres',
|
||||
themeImportErrorBody: 'Det samsvarer ikke med Psysonic-temaformatet. Det kan være laget for en annen versjon, eller filen er ufullstendig — sjekk at du valgte riktig .zip, eller spør temaets forfatter.',
|
||||
themeImportErrorDetails: 'Tekniske detaljer',
|
||||
themeImportConfirmTitle: 'Installere tema?',
|
||||
themeImportConfirmBody: 'Installere «{{name}}» av {{author}}?',
|
||||
tabStorage: 'Frakoblet & Cache',
|
||||
inputKeybindingsTitle: 'Tastatursnarveier',
|
||||
aboutContributorsCount_one: '{{count}} bidrag',
|
||||
|
||||
@@ -350,6 +350,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Vorige pagina',
|
||||
themeStorePageNext: 'Volgende pagina',
|
||||
themeStorePageStatus: 'Pagina {{page}} van {{total}}',
|
||||
themeImportTitle: 'Een thema importeren',
|
||||
themeImportButton: 'Thema importeren…',
|
||||
themeImporting: 'Importeren…',
|
||||
themeImportHint: 'Laadt een thema verpakt als .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '"{{name}}" geïmporteerd.',
|
||||
themeImportErrorTitle: 'Dit thema kon niet worden geïmporteerd',
|
||||
themeImportErrorBody: 'Het komt niet overeen met het Psysonic-themaformaat. Mogelijk is het voor een andere versie gemaakt of is het bestand onvolledig — controleer of je de juiste .zip hebt gekozen, of vraag de maker van het thema.',
|
||||
themeImportErrorDetails: 'Technische details',
|
||||
themeImportConfirmTitle: 'Thema installeren?',
|
||||
themeImportConfirmBody: '"{{name}}" van {{author}} installeren?',
|
||||
tabLibrary: 'Bibliotheek',
|
||||
tabServers: 'Servers',
|
||||
tabLyrics: 'Songteksten',
|
||||
|
||||
@@ -356,6 +356,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Pagina anterioară',
|
||||
themeStorePageNext: 'Pagina următoare',
|
||||
themeStorePageStatus: 'Pagina {{page}} din {{total}}',
|
||||
themeImportTitle: 'Importă o temă',
|
||||
themeImportButton: 'Importă tema…',
|
||||
themeImporting: 'Se importă…',
|
||||
themeImportHint: 'Încarcă o temă împachetată ca .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '„{{name}}” importată.',
|
||||
themeImportErrorTitle: 'Această temă nu a putut fi importată',
|
||||
themeImportErrorBody: 'Nu corespunde formatului de teme Psysonic. Poate a fost creată pentru altă versiune sau fișierul este incomplet — verifică dacă ai ales arhiva .zip corectă sau întreabă autorul temei.',
|
||||
themeImportErrorDetails: 'Detalii tehnice',
|
||||
themeImportConfirmTitle: 'Instalezi tema?',
|
||||
themeImportConfirmBody: 'Instalezi „{{name}}” de {{author}}?',
|
||||
tabLibrary: 'Librărie',
|
||||
tabServers: 'Servere',
|
||||
tabLyrics: 'Versuri',
|
||||
|
||||
@@ -432,6 +432,16 @@ export const settings = {
|
||||
themeStorePagePrev: 'Предыдущая страница',
|
||||
themeStorePageNext: 'Следующая страница',
|
||||
themeStorePageStatus: 'Страница {{page}} из {{total}}',
|
||||
themeImportTitle: 'Импорт темы',
|
||||
themeImportButton: 'Импортировать тему…',
|
||||
themeImporting: 'Импорт…',
|
||||
themeImportHint: 'Загружает тему, упакованную в .zip (manifest.json + theme.css).',
|
||||
themeImportSuccess: '«{{name}}» импортирована.',
|
||||
themeImportErrorTitle: 'Не удалось импортировать эту тему',
|
||||
themeImportErrorBody: 'Он не соответствует формату тем Psysonic. Возможно, он создан для другой версии или файл неполный — проверьте, что выбрали правильный .zip, или обратитесь к автору темы.',
|
||||
themeImportErrorDetails: 'Технические подробности',
|
||||
themeImportConfirmTitle: 'Установить тему?',
|
||||
themeImportConfirmBody: 'Установить «{{name}}» от {{author}}?',
|
||||
tabLibrary: 'Библиотека',
|
||||
tabServers: 'Серверы',
|
||||
tabLyrics: 'Тексты песен',
|
||||
|
||||
@@ -349,6 +349,16 @@ export const settings = {
|
||||
themeStorePagePrev: '上一页',
|
||||
themeStorePageNext: '下一页',
|
||||
themeStorePageStatus: '第 {{page}} 页,共 {{total}} 页',
|
||||
themeImportTitle: '导入主题',
|
||||
themeImportButton: '导入主题…',
|
||||
themeImporting: '正在导入…',
|
||||
themeImportHint: '加载打包为 .zip 的主题(manifest.json + theme.css)。',
|
||||
themeImportSuccess: '已导入“{{name}}”。',
|
||||
themeImportErrorTitle: '无法导入此主题',
|
||||
themeImportErrorBody: '它不符合 Psysonic 主题格式。可能是为其他版本制作的,或文件不完整——请确认选择了正确的 .zip,或联系主题作者。',
|
||||
themeImportErrorDetails: '技术细节',
|
||||
themeImportConfirmTitle: '安装主题?',
|
||||
themeImportConfirmBody: '安装来自 {{author}} 的“{{name}}”?',
|
||||
tabLibrary: '媒体库',
|
||||
tabServers: '服务器',
|
||||
tabLyrics: '歌词',
|
||||
|
||||
Reference in New Issue
Block a user