feat: Data-based QuickLook extension for macOS 12+ (#7213)

* feat: Prepate Xcode project skeleton for QuickLookExtension

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Add initial implementation of QuickLookExtension

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Add working Data-based QuickLook Preview

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Add new style.css

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Copy Localization.strings as is from legacy QuickLook plugin

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* fixup: Fix compilation missing NSStringAdditions

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* fixup: Apply clang-format, update style.css, fix file name reference

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Adopt CMake build from legacy QuickLook Plugin

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* fixup: Remove counter for debug

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* fixup: Make header row for file span across all table

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* feat: Improve style.css

- Use CSS Vars
- Add missing paddings
- Add sticky headers for tables

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Use UTType for Image generation more directly.

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Fixup for Xcode project checks in PR

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Fix Copyright and remove Xcode generated comments

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Remove unneeded fields from targets Info.plist template.

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Move tx localization to new QuickLook Extension

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* Apply code review suggestions

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>

---------

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
This commit is contained in:
Dzmitry Neviadomski
2025-10-31 19:30:55 +03:00
committed by GitHub
parent 1d0db31ae7
commit 9b496350a9
30 changed files with 1439 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ add_link_options(
# equivalent of XCODE_ATTRIBUTE_DEAD_CODE_STRIPPING YES for this directory
$<$<LINK_LANGUAGE:C,CXX>:-dead_strip>)
add_subdirectory(QuickLookExtension)
add_subdirectory(QuickLookPlugin)
add_subdirectory(VDKQueue)

View File

@@ -0,0 +1,117 @@
set(MAC_QL_BUNDLE_NAME QuickLookExtension)
add_library(${TR_NAME}-mac-qlappex MODULE)
target_sources(${TR_NAME}-mac-qlappex
PRIVATE
../NSStringAdditions.mm
PreviewProvider.mm)
set(LINGUAS
da
de
en
es
eu
fr
he
hu
it
ja
nl
pl
pt-BR
pt-PT
ru
sv
tr
uk
zh-CN
zh-TW)
if(ENABLE_NLS)
set(ENABLED_LINGUAS ${LINGUAS})
else()
set(ENABLED_LINGUAS en)
endif()
set(LANG_STRINGS_FILES
Localizable.strings)
set(RESOURCES
style.css)
source_group(Resources
FILES ${RESOURCES})
set_source_files_properties(
${RESOURCES}
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
foreach(LANG ${ENABLED_LINGUAS})
set(${LANG}_STRINGS_FILES)
set(STRINGS_FILES_RESOURCES_DIR Resources)
if(NOT CMAKE_GENERATOR STREQUAL Xcode)
string(APPEND STRINGS_FILES_RESOURCES_DIR /${LANG}.lproj)
endif()
foreach(F ${LANG_STRINGS_FILES})
list(APPEND ${LANG}_STRINGS_FILES ${LANG}.lproj/${F})
list(APPEND RESOURCES ${${LANG}_STRINGS_FILES})
set_source_files_properties(
${${LANG}_STRINGS_FILES}
PROPERTIES
MACOSX_PACKAGE_LOCATION ${STRINGS_FILES_RESOURCES_DIR})
endforeach()
source_group(Resources/${LANG}.lproj
FILES ${${LANG}_STRINGS_FILES})
endforeach()
target_sources(${TR_NAME}-mac-qlappex
PRIVATE
${RESOURCES})
set_target_properties(
${TR_NAME}-mac-qlappex
PROPERTIES
BUNDLE ON
BUNDLE_EXTENSION appex
INSTALL_RPATH "@loader_path/../../../../MacOS;@loader_path/../../../../Frameworks"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "12.0"
MACOSX_BUNDLE ON
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
OUTPUT_NAME ${MAC_QL_BUNDLE_NAME})
target_compile_options(
${TR_NAME}-mac-qlappex
PUBLIC
"-mmacosx-version-min=12.0")
target_link_options(
${TR_NAME}-mac-qlappex
PUBLIC
"-mmacosx-version-min=12.0")
target_include_directories(${TR_NAME}-mac-qlappex
PRIVATE
..)
target_link_libraries(${TR_NAME}-mac-qlappex
PRIVATE
${TR_NAME}
"-framework Foundation"
"-framework CoreFoundation"
"-framework CoreServices"
"-framework Quartz"
"-framework QuickLook")
install(
TARGETS ${TR_NAME}-mac-qlappex
DESTINATION Applications/${MAC_BUNDLE_NAME}.app/Contents/PlugIns)
install(CODE
"list(APPEND CMAKE_MODULE_PATH \"${PROJECT_SOURCE_DIR}/cmake\")
include(TrMacros)
include(GetPrerequisites)
tr_fixup_bundle_item(
\"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/Applications/${MAC_BUNDLE_NAME}.app\"
\"Contents/PlugIns/${MAC_QL_BUNDLE_NAME}.appex/Contents/MacOS/${MAC_QL_BUNDLE_NAME}\" \"\")")

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>QLIsDataBasedPreview</key>
<true/>
<key>QLSupportedContentTypes</key>
<array>
<string>org.bittorrent.torrent</string>
</array>
<key>QLSupportsSearchableItems</key>
<false/>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.preview</string>
<key>NSExtensionPrincipalClass</key>
<string>PreviewProvider</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>QuickLookExtension</string>
<key>CFBundleExecutable</key>
<string>QuickLookExtension</string>
<key>CFBundleIdentifier</key>
<string>org.m0k.transmission.QuickLookExtension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>QuickLookExtension</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>QLIsDataBasedPreview</key>
<true/>
<key>QLSupportedContentTypes</key>
<array>
<string>org.bittorrent.torrent</string>
</array>
<key>QLSupportsSearchableItems</key>
<false/>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.quicklook.preview</string>
<key>NSExtensionPrincipalClass</key>
<string>PreviewProvider</string>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2024 The Transmission Project. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
// This file Copyright © Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@interface PreviewProvider : QLPreviewProvider<QLPreviewingController>
@end

View File

@@ -0,0 +1,290 @@
// This file Copyright © Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import "PreviewProvider.h"
#include <string>
#include <unordered_map>
#include <vector>
#include <libtransmission/torrent-metainfo.h>
#include <libtransmission/utils.h>
#import "NSStringAdditions.h"
static NSUInteger const kIconWidth = 16;
namespace
{
class FileTreeNode
{
public:
FileTreeNode() = default;
~FileTreeNode() = default;
auto MaybeCreateChild(std::string_view child_name)
{
return children_.try_emplace(std::string{ child_name });
}
private:
FileTreeNode(FileTreeNode const&) = delete;
FileTreeNode& operator=(FileTreeNode&) = delete;
std::unordered_map<std::string, FileTreeNode> children_;
};
} // namespace
NSString* generateIconData(UTType* type, NSUInteger width, NSMutableDictionary<NSString*, QLPreviewReplyAttachment*>* allImgProps)
{
// We need to do this once per file type, per image size
NSString* iconFileName = [NSString stringWithFormat:@"%@.%ld.tiff", type.identifier, width];
if (!allImgProps[iconFileName])
{
NSImage* icon = [NSWorkspace.sharedWorkspace iconForContentType:type];
NSRect const iconFrame = NSMakeRect(0.0, 0.0, width, width);
NSImage* renderedIcon = [[NSImage alloc] initWithSize:iconFrame.size];
[renderedIcon lockFocus];
[icon drawInRect:iconFrame];
[renderedIcon unlockFocus];
NSData* iconData = renderedIcon.TIFFRepresentation;
QLPreviewReplyAttachment* imageAttachment = [[QLPreviewReplyAttachment alloc] initWithData:iconData contentType:UTTypePNG];
allImgProps[iconFileName] = imageAttachment;
}
return [@"cid:" stringByAppendingString:iconFileName];
}
@implementation PreviewProvider
- (void)providePreviewForFileRequest:(QLFilePreviewRequest*)request
completionHandler:(void (^)(QLPreviewReply* _Nullable reply, NSError* _Nullable error))handler
{
QLPreviewReply* reply = [[QLPreviewReply alloc]
initWithDataOfContentType:UTTypeHTML
contentSize:CGSizeMake(1200, 800)
dataCreationBlock:^NSData* _Nullable(QLPreviewReply* _Nonnull replyToUpdate, NSError* __autoreleasing _Nullable* _Nullable error) {
NSString* previewHTML = [self generateHTMLPreviewFor:request.fileURL andReply:replyToUpdate];
return [previewHTML dataUsingEncoding:NSUTF8StringEncoding];
}];
handler(reply, nil);
}
- (NSString*)generateHTMLPreviewFor:(NSURL*)url andReply:(QLPreviewReply* _Nonnull)replyToUpdate
{
// Try to parse the torrent file
auto metainfo = tr_torrent_metainfo{};
if (!metainfo.parse_torrent_file(url.fileSystemRepresentation))
{
return nil;
}
NSBundle* bundle = NSBundle.mainBundle;
NSURL* styleURL = [bundle URLForResource:@"style" withExtension:@"css"];
NSString* styleContents = [NSString stringWithContentsOfURL:styleURL encoding:NSUTF8StringEncoding error:NULL];
NSMutableString* htmlString = [NSMutableString string];
[htmlString appendFormat:@"<html><style type=\"text/css\">%@</style><body>", styleContents];
NSMutableDictionary<NSString*, QLPreviewReplyAttachment*>* attachments = [NSMutableDictionary dictionary];
NSString* name = @(metainfo.name().c_str());
auto const n_files = metainfo.file_count();
auto const is_multifile = n_files > 1;
UTType* fileType = is_multifile ? UTTypeFolder : [UTType typeWithFilenameExtension:name.pathExtension];
NSUInteger const width = 32;
[htmlString appendFormat:@"<h2><img class=\"icon\" src=\"%@\" width=\"%ld\" height=\"%ld\" />%@</h2>",
generateIconData(fileType, width, attachments),
width,
width,
name];
NSString* fileSizeString = [NSString stringForFileSize:metainfo.total_size()];
if (is_multifile)
{
NSString* fileCountString = [NSString
localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"%lu files", nil, bundle, "quicklook file count"), n_files];
fileSizeString = [NSString stringWithFormat:@"%@, %@", fileCountString, fileSizeString];
}
[htmlString appendFormat:@"<p>%@</p>", fileSizeString];
auto const date_created = metainfo.date_created();
NSString* dateCreatedString = date_created > 0 ?
[NSDateFormatter localizedStringFromDate:[NSDate dateWithTimeIntervalSince1970:date_created] dateStyle:NSDateFormatterLongStyle
timeStyle:NSDateFormatterShortStyle] :
nil;
auto const& creator = metainfo.creator();
NSString* creatorString = !std::empty(creator) ? @(creator.c_str()) : nil;
if ([creatorString isEqualToString:@""])
{
creatorString = nil;
}
NSString* creationString = nil;
if (dateCreatedString && creatorString)
{
creationString = [NSString
stringWithFormat:NSLocalizedStringFromTableInBundle(@"Created on %@ with %@", nil, bundle, "quicklook creation info"),
dateCreatedString,
creatorString];
}
else if (dateCreatedString)
{
creationString = [NSString
stringWithFormat:NSLocalizedStringFromTableInBundle(@"Created on %@", nil, bundle, "quicklook creation info"), dateCreatedString];
}
else if (creatorString)
{
creationString = [NSString
stringWithFormat:NSLocalizedStringFromTableInBundle(@"Created with %@", nil, bundle, "quicklook creation info"), creatorString];
}
if (creationString)
{
[htmlString appendFormat:@"<p>%@</p>", creationString];
}
auto const& commentStr = metainfo.comment();
if (!std::empty(commentStr))
{
NSString* comment = @(commentStr.c_str());
if (![comment isEqualToString:@""])
[htmlString appendFormat:@"<p>%@</p>", comment];
}
NSMutableArray* lists = [NSMutableArray array];
auto const n_webseeds = metainfo.webseed_count();
if (n_webseeds > 0)
{
NSMutableString* listSection = [NSMutableString string];
[listSection appendString:@"<table>"];
NSString* headerTitleString = n_webseeds == 1 ?
NSLocalizedStringFromTableInBundle(@"1 Web Seed", nil, bundle, "quicklook web seed header") :
[NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"%lu Web Seeds", nil, bundle, "quicklook web seed header"),
n_webseeds];
[listSection appendFormat:@"<tr><th>%@</th></tr>", headerTitleString];
for (size_t i = 0; i < n_webseeds; ++i)
{
[listSection appendFormat:@"<tr><td>%s</td></tr>", metainfo.webseed(i).c_str()];
}
[listSection appendString:@"</table>"];
[lists addObject:listSection];
}
auto const& announce_list = metainfo.announce_list();
if (!std::empty(announce_list))
{
NSMutableString* listSection = [NSMutableString string];
[listSection appendString:@"<table>"];
auto const n = std::size(announce_list);
NSString* headerTitleString = n == 1 ?
NSLocalizedStringFromTableInBundle(@"1 Tracker", nil, bundle, "quicklook tracker header") :
[NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"%lu Trackers", nil, bundle, "quicklook tracker header"), n];
[listSection appendFormat:@"<tr><th>%@</th></tr>", headerTitleString];
// TODO: handle tiers?
for (auto const& tracker : announce_list)
{
[listSection appendFormat:@"<tr><td>%s</td></tr>", tracker.announce.c_str()];
}
[listSection appendString:@"</table>"];
[lists addObject:listSection];
}
if (is_multifile)
{
NSMutableString* listSection = [NSMutableString string];
[listSection appendString:@"<table>"];
NSString* fileTitleString = [NSString
localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"%lu Files", nil, bundle, "quicklook file header"), n_files];
[listSection appendFormat:@"<tr><th colspan=\"2\">%@</th></tr>", fileTitleString];
FileTreeNode root{};
for (auto const& [path, size] : metainfo.files().sorted_by_path())
{
FileTreeNode* curNode = &root;
size_t level = 0;
auto subpath = std::string_view{ path };
auto path_vec = std::vector<std::string_view>{};
auto token = std::string_view{};
while (tr_strv_sep(&subpath, &token, '/'))
{
path_vec.emplace_back(token);
}
size_t const last = path_vec.size() - 1;
for (auto const& part : path_vec)
{
auto [it, inserted] = curNode->MaybeCreateChild(part);
if (inserted)
{
NSString* pathPart = @(it->first.c_str());
UTType* pathType = nil;
NSString* fileSize = nil;
if (level < last)
{
// This node is a directory.
pathType = UTTypeFolder;
fileSize = @"";
}
else
{
// This node is a leaf file.
pathType = [UTType typeWithFilenameExtension:pathPart.pathExtension];
fileSize = [NSString stringForFileSize:size];
}
[listSection appendFormat:@"<tr><td><img style=\"padding-left: %ldpx\" class=\"icon\" src=\"%@\" width=\"%ld\" height=\"%ld\" />%@</td><td class=\"grey\">%@</td></tr>",
level * kIconWidth,
generateIconData(pathType, kIconWidth, attachments),
kIconWidth,
kIconWidth,
pathPart,
fileSize];
}
curNode = &it->second;
level++;
}
}
[listSection appendString:@"</table>"];
[lists addObject:listSection];
}
if (lists.count > 0)
{
[htmlString appendFormat:@"<hr/><br>%@", [lists componentsJoinedByString:@"<br>"]];
}
[htmlString appendString:@"</body></html>"];
replyToUpdate.attachments = attachments;
return htmlString;
}
@end

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Filer";
/* quicklook file count */
"%lu files" = "%lu filer";
/* quicklook tracker header */
"%lu Trackers" = "%lu Trackere";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Webseeds";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 webseed";
/* quicklook creation info */
"Created on %@" = "Oprettet %@";
/* quicklook creation info */
"Created on %@ with %@" = "Oprettet %1$@ med %2$@";
/* quicklook creation info */
"Created with %@" = "Oprettet med %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Dateien";
/* quicklook file count */
"%lu files" = "%lu Dateien";
/* quicklook tracker header */
"%lu Trackers" = "%lu Tracker";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Web-Verteiler";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 Web-Verteiler";
/* quicklook creation info */
"Created on %@" = "Erstellt am %@";
/* quicklook creation info */
"Created on %@ with %@" = "Erstellt am %1$@ mit %2$@";
/* quicklook creation info */
"Created with %@" = "Erstellt mit %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Files";
/* quicklook file count */
"%lu files" = "%lu files";
/* quicklook tracker header */
"%lu Trackers" = "%lu Trackers";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Web Seeds";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 Web Seed";
/* quicklook creation info */
"Created on %@" = "Created on %@";
/* quicklook creation info */
"Created on %@ with %@" = "Created on %1$@ with %2$@";
/* quicklook creation info */
"Created with %@" = "Created with %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Archivos";
/* quicklook file count */
"%lu files" = "%lu archivos";
/* quicklook tracker header */
"%lu Trackers" = "%lu Trackers";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Semillas Web";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 Semilla Web";
/* quicklook creation info */
"Created on %@" = "Creado el %@";
/* quicklook creation info */
"Created on %@ with %@" = "Creado el %1$@ con %2$@";
/* quicklook creation info */
"Created with %@" = "Creado con %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Fitxategiak";
/* quicklook file count */
"%lu files" = "%lu fitxategiak";
/* quicklook tracker header */
"%lu Trackers" = "%lu Aztarnariak";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Web Igorleak";
/* quicklook tracker header */
"1 Tracker" = "Aztarnari 1";
/* quicklook web seed header */
"1 Web Seed" = "1 Web hazia";
/* quicklook creation info */
"Created on %@" = "%@ Sortua ";
/* quicklook creation info */
"Created on %@ with %@" = "Honekin %2$@ %1$@ Sortua ";
/* quicklook creation info */
"Created with %@" = "%@ Honekin sortua";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu fichiers";
/* quicklook file count */
"%lu files" = "%lu fichiers";
/* quicklook tracker header */
"%lu Trackers" = "%lu traceurs";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu semences Web";
/* quicklook tracker header */
"1 Tracker" = "1 traceur";
/* quicklook web seed header */
"1 Web Seed" = "1 semence Web";
/* quicklook creation info */
"Created on %@" = "Créé le %@";
/* quicklook creation info */
"Created on %@ with %@" = "Créé le %1$@ avec %2$@";
/* quicklook creation info */
"Created with %@" = "Créé avec %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu קבצים";
/* quicklook file count */
"%lu files" = "%lu קבצים";
/* quicklook tracker header */
"%lu Trackers" = "%lu עוקבים";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu זורעי רשת";
/* quicklook tracker header */
"1 Tracker" = "עוקב אחד";
/* quicklook web seed header */
"1 Web Seed" = "זורע רשת אחד";
/* quicklook creation info */
"Created on %@" = "נוצר ב־%@";
/* quicklook creation info */
"Created on %@ with %@" = "נוצר ב־%1$@ עם %2$@";
/* quicklook creation info */
"Created with %@" = "נוצר עם %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Fájl";
/* quicklook file count */
"%lu files" = "%lu Fájl";
/* quicklook tracker header */
"%lu Trackers" = "%lu Tracker";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Web seed";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 Web seed";
/* quicklook creation info */
"Created on %@" = "Létrehozva ekkor: %@";
/* quicklook creation info */
"Created on %@ with %@" = "Létrehozva ekkor: %1$@, ezzel: %2$@";
/* quicklook creation info */
"Created with %@" = "Létrehozva ezzel: %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu file";
/* quicklook file count */
"%lu files" = "%lu file";
/* quicklook tracker header */
"%lu Trackers" = "%lu server traccia";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu seeder web";
/* quicklook tracker header */
"1 Tracker" = "1 server traccia";
/* quicklook web seed header */
"1 Web Seed" = "1 seeder web";
/* quicklook creation info */
"Created on %@" = "Creato il %@";
/* quicklook creation info */
"Created on %@ with %@" = "Creato il %1$@ con %2$@";
/* quicklook creation info */
"Created with %@" = "Creato con %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%luファイル";
/* quicklook file count */
"%lu files" = "%luファイル";
/* quicklook tracker header */
"%lu Trackers" = "%luトラッカー";
/* quicklook web seed header */
"%lu Web Seeds" = "%luウェブシード";
/* quicklook tracker header */
"1 Tracker" = "1トラッカー";
/* quicklook web seed header */
"1 Web Seed" = "1ウェブシード";
/* quicklook creation info */
"Created on %@" = "作成日 %@";
/* quicklook creation info */
"Created on %@ with %@" = "作成日 %1$@ / 作成 %2$@";
/* quicklook creation info */
"Created with %@" = "作成 %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu bestanden";
/* quicklook file count */
"%lu files" = "%lu bestanden";
/* quicklook tracker header */
"%lu Trackers" = "%lu trackers";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu webseeds";
/* quicklook tracker header */
"1 Tracker" = "1 tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 webseed";
/* quicklook creation info */
"Created on %@" = "Gemaakt op %@";
/* quicklook creation info */
"Created on %@ with %@" = "Gemaakt op %1$@ met %2$@";
/* quicklook creation info */
"Created with %@" = "Gemaakt met %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu pliki(-ów)";
/* quicklook file count */
"%lu files" = "%lu pliki(-ów)";
/* quicklook tracker header */
"%lu Trackers" = "%lu serwery(-ów) śledzące(-ych)";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu wysyłające(-ych) w sieci";
/* quicklook tracker header */
"1 Tracker" = "1 serwer śledzący";
/* quicklook web seed header */
"1 Web Seed" = "1 wysyłający w sieci";
/* quicklook creation info */
"Created on %@" = "Utworzony dnia %@";
/* quicklook creation info */
"Created on %@ with %@" = "Utworzony dnia %1$@ przez: %2$@";
/* quicklook creation info */
"Created with %@" = "Utworzony za pomocą: %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Arquivos";
/* quicklook file count */
"%lu files" = "%lu arquivos";
/* quicklook tracker header */
"%lu Trackers" = "%lu Rastreadores";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Semeadores da Web";
/* quicklook tracker header */
"1 Tracker" = "1 Rastreador";
/* quicklook web seed header */
"1 Web Seed" = "1 Semeador Web";
/* quicklook creation info */
"Created on %@" = "Criado em %@";
/* quicklook creation info */
"Created on %@ with %@" = "Criado em %1$@ com %2$@";
/* quicklook creation info */
"Created with %@" = "Criado com %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Ficheiros";
/* quicklook file count */
"%lu files" = "%lu ficheiros";
/* quicklook tracker header */
"%lu Trackers" = "%lu Trackers";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Seeds web";
/* quicklook tracker header */
"1 Tracker" = "1 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 Seed Web";
/* quicklook creation info */
"Created on %@" = "Criado a %@";
/* quicklook creation info */
"Created on %@ with %@" = "Criado a %1$@ com o %2$@";
/* quicklook creation info */
"Created with %@" = "Criado com %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu файлов";
/* quicklook file count */
"%lu files" = "%lu файлов";
/* quicklook tracker header */
"%lu Trackers" = "%lu трекеров";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu сайтов раздачи";
/* quicklook tracker header */
"1 Tracker" = "1 трекер";
/* quicklook web seed header */
"1 Web Seed" = "1 сайт раздачи";
/* quicklook creation info */
"Created on %@" = "Создан %@";
/* quicklook creation info */
"Created on %@ with %@" = "Создан %1$@, с использованием %2$@";
/* quicklook creation info */
"Created with %@" = "Создан с использованием %@";

View File

@@ -0,0 +1,106 @@
:root {
color-scheme: light dark;
--bg-color: rgb(255, 255, 255);
--fg-color: rgb(0, 0, 0);
--header-text-color: rgb(50, 50, 50);
--header-underline-color: rgb(230, 230, 230);
--row-fg-color: rgb(40, 40, 40);
--alt-row-bg-color: rgb(245, 245, 245);
--alt-col-fg-color: rgb(133, 133, 133);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: rgb(30, 30, 30);
--fg-color: rgb(255, 255, 255);
--header-text-color: rgb(200, 200, 200);
--header-underline-color: rgb(55, 55, 55);
--row-fg-color: rgb(225, 225, 225);
--alt-row-bg-color: rgb(40, 40, 40);
--alt-col-fg-color: rgb(155, 155, 155);
}
}
html {
background: var(--bg-color);
color: var(--fg-color);
font-family: -apple-system, BlinkMacSystemFont, system-ui;
text-align: left;
}
table {
width: 100%;
border-spacing: 0;
}
/* Sticky Table Header Row */
tr:has(th) {
top: 0;
position: sticky;
background: var(--bg-color);
}
th {
padding-top: 8px;
color: var(--header-text-color);
border-bottom: 1px solid var(--header-underline-color);
font-weight: 500;
text-align: left;
}
tr:has(td):nth-child(odd) {
background: var(--alt-row-bg-color);
}
td {
color: var(--row-fg-color);
}
td.grey {
color: var(--alt-col-fg-color);
}
td:first-child {
padding-left: 8px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
td:last-child {
padding-right: 8px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
td:nth-child(1) {
text-align: left;
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
td:nth-child(2) {
text-align: right;
width: 80px;
}
img.icon {
margin-right: 8px;
vertical-align: text-bottom;
}

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu filer";
/* quicklook file count */
"%lu files" = "%lu filer";
/* quicklook tracker header */
"%lu Trackers" = "%lu spårare";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu webbdistributioner";
/* quicklook tracker header */
"1 Tracker" = "1 spårare";
/* quicklook web seed header */
"1 Web Seed" = "1 webbdistribution";
/* quicklook creation info */
"Created on %@" = "Skapades den %@";
/* quicklook creation info */
"Created on %@ with %@" = "Skapades den %1$@ med %2$@";
/* quicklook creation info */
"Created with %@" = "Skapades med %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Dosya";
/* quicklook file count */
"%lu files" = "%lu dosya";
/* quicklook tracker header */
"%lu Trackers" = "%lu İzleyici";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Web Beslemesi";
/* quicklook tracker header */
"1 Tracker" = "1 İzleyici";
/* quicklook web seed header */
"1 Web Seed" = "1 Web Beslemesi";
/* quicklook creation info */
"Created on %@" = "%@ üzerinde oluşturuldu";
/* quicklook creation info */
"Created on %@ with %@" = "%1$@ üzerinde %2$@ ile oluşturuldu";
/* quicklook creation info */
"Created with %@" = "%@ ile oluşturuldu";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu Файлів";
/* quicklook file count */
"%lu files" = "%lu файлів";
/* quicklook tracker header */
"%lu Trackers" = "%lu Відстежувачів";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu Висівів";
/* quicklook tracker header */
"1 Tracker" = "1 Відстежувач";
/* quicklook web seed header */
"1 Web Seed" = "1 Висів";
/* quicklook creation info */
"Created on %@" = "Створено %@";
/* quicklook creation info */
"Created on %@ with %@" = "Створено %1$@ з %2$@";
/* quicklook creation info */
"Created with %@" = "Створено з %@";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu 个文件";
/* quicklook file count */
"%lu files" = "%lu 文件";
/* quicklook tracker header */
"%lu Trackers" = "%lu 个 Tracker";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu 个网络种子";
/* quicklook tracker header */
"1 Tracker" = "1 个 Tracker";
/* quicklook web seed header */
"1 Web Seed" = "1 个网络种子";
/* quicklook creation info */
"Created on %@" = "创建于 %@";
/* quicklook creation info */
"Created on %@ with %@" = "由 %2$@ 创建于 %1$@";
/* quicklook creation info */
"Created with %@" = "由 %@ 创建";

View File

@@ -0,0 +1,27 @@
/* quicklook file header */
"%lu Files" = "%lu 個檔案";
/* quicklook file count */
"%lu files" = "%lu 個檔案";
/* quicklook tracker header */
"%lu Trackers" = "%lu 個追蹤器";
/* quicklook web seed header */
"%lu Web Seeds" = "%lu 個網路種子";
/* quicklook tracker header */
"1 Tracker" = "1 個追蹤器";
/* quicklook web seed header */
"1 Web Seed" = "1 個網路種子";
/* quicklook creation info */
"Created on %@" = "建立於 %@";
/* quicklook creation info */
"Created on %@ with %@" = "由 %2$@ 建立於 %1$@";
/* quicklook creation info */
"Created with %@" = "由 %@ 建立";