Translations

This commit is contained in:
p
2026-09-12 21:02:54 +02:00
parent 455599da2d
commit 781767527f
55 changed files with 1269 additions and 473 deletions
+1
View File
@@ -3,5 +3,6 @@
/build/
/build-*/
/libdnrs/Cargo.lock
/po/dawn.pot
compile_commands.json
.cache/
+5
View File
@@ -66,6 +66,7 @@ set(CMAKE_MODULE_PATH
find_package(PkgConfig REQUIRED)
find_package(Vulkan REQUIRED)
find_package(Intl REQUIRED)
# Maybe we need a newer version, just not to build.
find_package(Qt6 6.8 REQUIRED COMPONENTS Gui OPTIONAL_COMPONENTS WaylandClient)
find_program(AWK NAMES awk REQUIRED)
@@ -232,6 +233,9 @@ endif()
option(DAWN_WIP "Expose unfinished subapplications" OFF)
# CMAKE_INSTALL_FULL_LOCALEDIR goes into dawn-config.h.
include(GNUInstallDirs)
configure_file(
"${PROJECT_SOURCE_DIR}/dawn-config.h.in"
"${PROJECT_BINARY_DIR}/dawn-config.h")
@@ -264,6 +268,7 @@ add_subdirectory(dnthumbd)
if (LIBPNG_FOUND)
add_subdirectory(tools)
endif()
add_subdirectory(po)
add_subdirectory(dn)
if (BUILD_TESTING)
+3
View File
@@ -13,6 +13,9 @@
#define DAWN_NAMESPACE "@DAWN_NAMESPACE@"
#define DAWN_VERSION "@PROJECT_VERSION@"
// Relocatable packages find theirs next to the executable.
#define DAWN_LOCALEDIR "@CMAKE_INSTALL_FULL_LOCALEDIR@"
#if defined __GNUC__ || defined __clang__
#define DAWN_FORMAT(format_index, first_arg) \
__attribute__((format(printf, format_index, first_arg)))
+52
View File
@@ -0,0 +1,52 @@
//
// dawn-gettext.h: message translation macros
//
// Copyright The Dawn Authors
// SPDX-License-Identifier: MPL-2.0
//
#pragma once
#include <dawn-config.h>
#include <libintl.h>
#include <string>
/// Dawn ships one catalogue for the application and the library alike.
/// Every lookup names it explicitly, so that libdn keeps working inside
/// a host that has bound a text domain of its own.
#define DAWN_TEXTDOMAIN "dawn"
/// Translate a literal.
#define _(s) dgettext(DAWN_TEXTDOMAIN, (s))
/// Mark a literal for extraction, to be translated with _() where it is used.
#define N_(s) (s)
/// Translate a literal that needs disambiguating from an identical one.
#define C_(context, s) dawn_pgettext(context "\004" s, (s))
/// Translate a countable literal.
#define P_(s, p, n) dngettext(DAWN_TEXTDOMAIN, (s), (p), (n))
/// C_() backend. A miss returns the argument itself, glued context and all.
static inline const char *
dawn_pgettext(const char *msgctxtid, const char *msgid)
{
const char *translation = dgettext(DAWN_TEXTDOMAIN, msgctxtid);
return translation == msgctxtid ? msgid : translation;
}
namespace dawn
{
/// Pick the user's language, bind the Dawn catalogue, and have gettext answer
/// in UTF-8. Executables call this before they build any translated string
/// or start any worker thread. libdn never touches the process locale on its
/// own, so a host that skips this step simply gets the English originals.
void gettext_init();
/// Build a message out of a translated format string. A format string is
/// only ever a variable because it has been through gettext, which tells the
/// compiler as much, so the usual checking still applies at the call site.
std::string format_message(const char *format, ...) DAWN_FORMAT(1, 2);
} // namespace dawn
+41 -7
View File
@@ -239,7 +239,7 @@ else()
set(desktop "${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.desktop")
if (NOT CMAKE_CROSSCOMPILING OR CMAKE_CROSSCOMPILING_EMULATOR)
set(generated
"${CMAKE_CURRENT_BINARY_DIR}/${DAWN_NAMESPACE}.desktop")
"${CMAKE_CURRENT_BINARY_DIR}/${DAWN_NAMESPACE}.mime.desktop")
add_custom_command(OUTPUT "${generated}"
COMMAND ${CMAKE_COMMAND}
"-DEMULATOR=${CMAKE_CROSSCOMPILING_EMULATOR}"
@@ -249,17 +249,38 @@ else()
DEPENDS dn "${desktop}"
COMMENT "Filling in desktop file media types"
VERBATIM)
add_custom_target(desktop-media-types ALL DEPENDS "${generated}")
set(desktop "${generated}")
endif()
set(desktops "${desktop}"
"${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.Browse.desktop")
if (DAWN_WIP)
install(FILES
list(APPEND desktops
"${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.CropJpeg.desktop"
"${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.Commander.desktop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
"${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.Commander.desktop")
endif()
install(FILES "${desktop}"
"${CMAKE_CURRENT_SOURCE_DIR}/${DAWN_NAMESPACE}.Browse.desktop"
# What the launcher says is translated from the same catalogue as the
# application, which is why the names in it are never invented here.
set(entries)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/applications")
foreach (template IN LISTS desktops)
get_filename_component(entry "${template}" NAME)
string(REPLACE ".mime." "." entry "${entry}")
set(output "${CMAKE_CURRENT_BINARY_DIR}/applications/${entry}")
add_custom_command(OUTPUT "${output}"
COMMAND "${MSGFMT_EXECUTABLE}" --desktop
--keyword=Name --keyword=GenericName
--keyword=X-GNOME-FullName
"--template=${template}" -d "${PROJECT_SOURCE_DIR}/po"
-o "${output}"
DEPENDS "${template}" ${DAWN_TRANSLATIONS}
COMMENT "Translating ${entry}"
VERBATIM)
list(APPEND entries "${output}")
endforeach()
add_custom_target(desktop-entries ALL DEPENDS ${entries})
install(FILES ${entries}
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
endif()
endif()
@@ -365,6 +386,19 @@ else()
endforeach()
endif()
# Message catalogues. The bundle's Resources have to be complete already
# in the build tree; the installation copies the bundle as it finds it.
add_dependencies(dn catalogues)
if (APPLE AND DAWN_CATALOGUES)
foreach (mo IN LISTS DAWN_CATALOGUES)
file(RELATIVE_PATH rel "${DAWN_LOCALE_DIR}" "${mo}")
get_filename_component(rel "${rel}" DIRECTORY)
set_source_files_properties("${mo}" PROPERTIES
MACOSX_PACKAGE_LOCATION "Resources/share/locale/${rel}")
endforeach()
target_sources(dn PRIVATE ${DAWN_CATALOGUES})
endif()
if (BUILD_TESTING)
add_test(NAME dn-help COMMAND $<TARGET_FILE:dn> --help)
endif()
+97 -81
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "action.hpp"
#include <QByteArray>
@@ -34,12 +36,15 @@ constexpr unsigned kShift = unsigned(Qt::ShiftModifier);
// clang-format off
constexpr ActionDef kDefs[] = {
{},
{kMenu, {"_New Window"}, {}, {{Qt::Key_N, kCtrl}}, {}},
{kMenu, {"_Close Window"}, {}, {{Qt::Key_W, kCtrl}, {Qt::Key_Q}}, {}},
{0, {"_Minimise"}, {}, {}, {}},
{kToggle, {"_Maximise", "Res_tore"}, {}, {}, {}},
{kMenu, {"_Quit"}, {}, {{Qt::Key_Q, kCtrl}}, {}},
{kToggle, {"Enter _Full Screen", "Exit _Full Screen"},
// TRANSLATORS: The underscore in a label marks the mnemonic, the letter
// that follows it. Every label in this file works that way; keep one,
// on a letter that no other entry of the same menu has taken.
{kMenu, {N_("_New Window")}, {}, {{Qt::Key_N, kCtrl}}, {}},
{kMenu, {N_("_Close Window")}, {}, {{Qt::Key_W, kCtrl}, {Qt::Key_Q}}, {}},
{0, {N_("_Minimise")}, {}, {}, {}},
{kToggle, {N_("_Maximise"), N_("Res_tore")}, {}, {}, {}},
{kMenu, {N_("_Quit")}, {}, {{Qt::Key_Q, kCtrl}}, {}},
{kToggle, {N_("Enter _Full Screen"), N_("Exit _Full Screen")},
{"view-fullscreen-symbolic", "view-restore-symbolic"}, {
#ifdef Q_OS_MACOS
{Qt::Key_F, kCtrl | kMeta},
@@ -47,92 +52,93 @@ constexpr ActionDef kDefs[] = {
{Qt::Key_F11},
#endif
}, {}},
{kToggle, {"_Dark Mode"}, {"dark-mode-symbolic"}, {{Qt::Key_D}}, {}},
{kMenu, {"_Hint"}, {}, {{Qt::Key_F}}, {}},
{kMenu, {"_Back in History"}, {"curved-arrow-left-symbolic"}, {
{kToggle, {N_("_Dark Mode")}, {"dark-mode-symbolic"}, {{Qt::Key_D}}, {}},
// TRANSLATORS: Labels every clickable thing with a letter to type.
{kMenu, {N_("_Hint")}, {}, {{Qt::Key_F}}, {}},
{kMenu, {N_("_Back in History")}, {"curved-arrow-left-symbolic"}, {
#ifdef Q_OS_MACOS
{Qt::Key_BracketLeft, kCtrl},
#else
{Qt::Key_Left, kAlt},
#endif
{Qt::Key_Backspace}}, {}},
{kMenu, {"_Forward in History"}, {"curved-arrow-right-symbolic"}, {
{kMenu, {N_("_Forward in History")}, {"curved-arrow-right-symbolic"}, {
#ifdef Q_OS_MACOS
{Qt::Key_BracketRight, kCtrl},
#else
{Qt::Key_Right, kAlt},
#endif
}, {}},
{kMenu, {"_Location..."}, {}, {
{kMenu, {N_("_Location...")}, {}, {
#ifdef Q_OS_MACOS
{Qt::Key_G, kCtrl | kShift},
#else
{Qt::Key_L, kCtrl},
#endif
}, {}},
{kMenu, {"_Contents"}, {}, {
{kMenu, {N_("_Contents")}, {}, {
#ifdef Q_OS_MACOS
{Qt::Key_Question, kCtrl},
#else
{Qt::Key_F1},
#endif
}, {}},
{kMenu, {"_About"}, {}, {}, {}},
{kMenu, {"_Keyboard Shortcuts"}, {}, {
{kMenu, {N_("_About")}, {}, {}, {}},
{kMenu, {N_("_Keyboard Shortcuts")}, {}, {
#ifndef Q_OS_MACOS
{Qt::Key_Question, kCtrl},
#endif
}, {}},
{kMenu, {"_Settings..."}, {}, {{Qt::Key_Comma, kCtrl}}, {}},
{kMenu, {N_("_Settings...")}, {}, {{Qt::Key_Comma, kCtrl}}, {}},
// TODO(p): Skip on macOS entirely, as it uses the global menu.
{0, {"_Menu"}, {}, {{Qt::Key_F10}}, {}},
{0, {N_("_Menu")}, {}, {{Qt::Key_F10}}, {}},
// The context menu action is mostly for documentation only.
{0, {"_Context Menu"}, {}, {{Qt::Key_Menu}, {Qt::Key_F10, kShift}}, {}},
{0, {"_Cancel"}, {}, {{Qt::Key_Escape}}, {}},
{0, {"_Next Pane"}, {}, {{Qt::Key_F6}}, {}},
{0, {"_Previous Pane"}, {}, {{Qt::Key_F6, kShift}}, {}},
{0, {N_("_Context Menu")}, {}, {{Qt::Key_Menu}, {Qt::Key_F10, kShift}}, {}},
{0, {N_("_Cancel")}, {}, {{Qt::Key_Escape}}, {}},
{0, {N_("_Next Pane")}, {}, {{Qt::Key_F6}}, {}},
{0, {N_("_Previous Pane")}, {}, {{Qt::Key_F6, kShift}}, {}},
{kToggle, {"Show _Sidebar"}, {"sidebar-left-symbolic"},
{kToggle, {N_("Show _Sidebar")}, {"sidebar-left-symbolic"},
{{Qt::Key_F9}}, {}},
{kMenu, {"_Previous Directory in Tree"}, {"go-previous-symbolic"},
{kMenu, {N_("_Previous Directory in Tree")}, {"go-previous-symbolic"},
{{Qt::Key_BracketLeft}}, {}},
{kMenu, {"_Next Directory in Tree"}, {"go-next-symbolic"},
{kMenu, {N_("_Next Directory in Tree")}, {"go-next-symbolic"},
{{Qt::Key_BracketRight}}, {}},
{kMenu, {"Parent _Directory"}, {"go-up-symbolic"}, {
{kMenu, {N_("Parent _Directory")}, {"go-up-symbolic"}, {
#ifdef Q_OS_MACOS
{Qt::Key_Up, kCtrl},
#else
{Qt::Key_Up, kAlt},
#endif
}, {}},
{kMenu, {"_Home"}, {}, {
{kMenu, {N_("_Home")}, {}, {
#ifdef Q_OS_MACOS
{Qt::Key_H, kCtrl | kShift},
#else
{Qt::Key_Home, kAlt},
#endif
}, {}},
{kMenu, {"S_maller Thumbnails"}, {"minus-framed-symbolic"},
{kMenu, {N_("S_maller Thumbnails")}, {"minus-framed-symbolic"},
{{Qt::Key_Minus}, {Qt::Key_Minus, kCtrl}}, {}},
{kMenu, {"_Larger Thumbnails"}, {"plus-framed-symbolic"},
{kMenu, {N_("_Larger Thumbnails")}, {"plus-framed-symbolic"},
{{Qt::Key_Plus}, {Qt::Key_Plus, kCtrl}}, {}},
{kToggle, {"Tiled _View"}, {"blocks-symbolic"},
{kToggle, {N_("Tiled _View")}, {"blocks-symbolic"},
{{Qt::Key_1}, {Qt::Key_1, kCtrl}}, {}},
{kToggle, {"_Grid View"}, {"view-grid-symbolic"},
{kToggle, {N_("_Grid View")}, {"view-grid-symbolic"},
{{Qt::Key_2}, {Qt::Key_2, kCtrl}}, {}},
{kToggle, {"L_ist View"}, {"view-list-symbolic"},
{kToggle, {N_("L_ist View")}, {"view-list-symbolic"},
{{Qt::Key_3}, {Qt::Key_3, kCtrl}}, {}},
{kToggle, {"Sho_w Filenames"}, {"font-symbolic"},
{kToggle, {N_("Sho_w Filenames")}, {"font-symbolic"},
{{Qt::Key_T}, {Qt::Key_T, kCtrl}}, {}},
{kToggle, {"Hide _Unsupported Files"}, {"filter-symbolic"},
{kToggle, {N_("Hide _Unsupported Files")}, {"filter-symbolic"},
{{Qt::Key_H}, {Qt::Key_H, kCtrl}}, {}},
{kToggle, {"Sort Des_cending", "Sort As_cending"},
{kToggle, {N_("Sort Des_cending"), N_("Sort As_cending")},
{"view-sort-descending-symbolic", "view-sort-ascending-symbolic"},
{{Qt::Key_C}}, {}},
{kToggle, {"Sort by _Name"}, {}, {{Qt::Key_1, kCtrl | kAlt}}, {}},
{kToggle, {"Sort by _Time"}, {}, {{Qt::Key_2, kCtrl | kAlt}}, {}},
{kMenu, {"_Filter"}, {}, {{Qt::Key_F, kCtrl}, {Qt::Key_Slash}}, {}},
{0, {"_Open"}, {}, {
{kToggle, {N_("Sort by _Name")}, {}, {{Qt::Key_1, kCtrl | kAlt}}, {}},
{kToggle, {N_("Sort by _Time")}, {}, {{Qt::Key_2, kCtrl | kAlt}}, {}},
{kMenu, {N_("_Filter")}, {}, {{Qt::Key_F, kCtrl}, {Qt::Key_Slash}}, {}},
{0, {N_("_Open")}, {}, {
// In Finder, Return nonsensically renames items.
{Qt::Key_Return}, {Qt::Key_Enter},
#ifdef Q_OS_MACOS
@@ -140,55 +146,59 @@ constexpr ActionDef kDefs[] = {
#endif
}, {}},
{kMenu, {"_Browse"}, {"blocks-symbolic"},
{kMenu, {N_("_Browse")}, {"blocks-symbolic"},
{{Qt::Key_Return}, {Qt::Key_Enter}}, {}},
{kMenu, {"_Previous File"}, {"go-previous-symbolic"},
{kMenu, {N_("_Previous File")}, {"go-previous-symbolic"},
{{Qt::Key_Left}, {Qt::Key_Up}, {Qt::Key_PageUp}}, {}},
{kMenu, {"_Next File"}, {"go-next-symbolic"},
{kMenu, {N_("_Next File")}, {"go-next-symbolic"},
{{Qt::Key_Right}, {Qt::Key_Down}, {Qt::Key_PageDown}}, {}},
{kMenu, {"Zoom _In"}, {"plus-framed-symbolic"},
{kMenu, {N_("Zoom _In")}, {"plus-framed-symbolic"},
{{Qt::Key_Plus}, {Qt::Key_Plus, kCtrl}}, {}},
{kMenu, {"Zoom _Out"}, {"minus-framed-symbolic"},
{kMenu, {N_("Zoom _Out")}, {"minus-framed-symbolic"},
{{Qt::Key_Minus}, {Qt::Key_Minus, kCtrl}}, {}},
{kMenu, {"O_riginal Size"}, {"one-framed-symbolic"},
{kMenu, {N_("O_riginal Size")}, {"one-framed-symbolic"},
{{Qt::Key_0, kCtrl}}, {}},
{0, {"Zoom _Level"}, {}, {}, "1-9"},
{kToggle, {"_Scale to Fit"}, {"zoom-fit-symbolic"}, {{Qt::Key_X}}, {}},
{0, {"Fit to _Width"}, {}, {{Qt::Key_W}}, {}},
{0, {"Fit to H_eight"}, {}, {{Qt::Key_H}}, {}},
{kToggle, {"_Lock View"},
// TRANSLATORS: The toolbar's zoom readout; "1-9" beside it is the range
// of digit keys that set it, and is not a label.
{0, {N_("Zoom _Level")}, {}, {}, "1-9"},
{kToggle, {N_("_Scale to Fit")}, {"zoom-fit-symbolic"}, {{Qt::Key_X}}, {}},
{0, {N_("Fit to _Width")}, {}, {{Qt::Key_W}}, {}},
{0, {N_("Fit to H_eight")}, {}, {{Qt::Key_H}}, {}},
{kToggle, {N_("_Lock View")},
{"padlock-open-symbolic", "padlock-closed-symbolic"},
{{Qt::Key_L}}, {}},
{kToggle, {"_Keep Zoom and Position"}, {"pin2-symbolic"},
// TRANSLATORS: Keeps the current zoom and scroll position across images.
{kToggle, {N_("_Keep Zoom and Position")}, {"pin2-symbolic"},
{{Qt::Key_K}}, {}},
{kToggle, {"_Colour Management"}, {"color-symbolic"}, {{Qt::Key_C}}, {}},
{kToggle, {"S_mooth Scaling"}, {"blend-tool-symbolic"}, {{Qt::Key_I}}, {}},
{kToggle, {"Highlight _Transparency"},
{kToggle, {N_("_Colour Management")}, {"color-symbolic"}, {{Qt::Key_C}}, {}},
{kToggle, {N_("S_mooth Scaling")}, {"blend-tool-symbolic"}, {{Qt::Key_I}}, {}},
{kToggle, {N_("Highlight _Transparency")},
{"transparent-background-symbolic"}, {{Qt::Key_T}}, {}},
{kToggle, {"Blend in Linear Light"}, {}, {}, {}},
{kMenu, {"Rotate _Left"}, {"rotate-acw-symbolic"}, {{Qt::Key_Less}}, {}},
{kMenu, {"_Mirror"}, {"flip-h-symbolic"}, {{Qt::Key_Equal}}, {}},
{kMenu, {"Rotate _Right"}, {"rotate-cw-symbolic"},
{kToggle, {N_("Blend in Linear Light")}, {}, {}, {}},
{kMenu, {N_("Rotate _Left")}, {"rotate-acw-symbolic"}, {{Qt::Key_Less}}, {}},
// TRANSLATORS: A verb: flips the image horizontally.
{kMenu, {N_("_Mirror")}, {"flip-h-symbolic"}, {{Qt::Key_Equal}}, {}},
{kMenu, {N_("Rotate _Right")}, {"rotate-cw-symbolic"},
{{Qt::Key_Greater}}, {}},
{kToggle, {"Show I_nformation"}, {"info-outline-symbolic"},
{kToggle, {N_("Show I_nformation")}, {"info-outline-symbolic"},
{{Qt::Key_Return, kAlt}, {Qt::Key_Enter, kAlt}}, {}},
{kMenu, {"_First Page"}, {"go-top-symbolic"}, {}, {}},
{kMenu, {"Pr_evious Page"}, {"go-up-symbolic"},
{kMenu, {N_("_First Page")}, {"go-top-symbolic"}, {}, {}},
{kMenu, {N_("Pr_evious Page")}, {"go-up-symbolic"},
{{Qt::Key_BracketLeft}}, {}},
{kMenu, {"_Next Page"}, {"go-down-symbolic"},
{kMenu, {N_("_Next Page")}, {"go-down-symbolic"},
{{Qt::Key_BracketRight}}, {}},
{kMenu, {"La_st Page"}, {"go-bottom-symbolic"}, {}, {}},
{kMenu, {"Re_wind"}, {"media-skip-backward-symbolic"}, {}, {}},
{kMenu, {"Pre_vious Frame"}, {"media-seek-backward-symbolic"},
{kMenu, {N_("La_st Page")}, {"go-bottom-symbolic"}, {}, {}},
{kMenu, {N_("Re_wind")}, {"media-skip-backward-symbolic"}, {}, {}},
{kMenu, {N_("Pre_vious Frame")}, {"media-seek-backward-symbolic"},
{{Qt::Key_BraceLeft}}, {}},
{kToggle, {"_Play", "_Pause"},
{kToggle, {N_("_Play"), N_("_Pause")},
{"media-playback-start-symbolic", "media-playback-pause-symbolic"},
{{Qt::Key_Space}}, {}},
{kMenu, {"Ne_xt Frame"}, {"media-seek-forward-symbolic"},
{kMenu, {N_("Ne_xt Frame")}, {"media-seek-forward-symbolic"},
{{Qt::Key_BraceRight}}, {}},
{0, {"_Copy"}, {}, {{Qt::Key_C, kCtrl}, {Qt::Key_Insert, kCtrl}}, {}},
{0, {"Move to _Trash"}, {}, {{Qt::Key_Delete}}, {}},
{kMenu, {"_Reload"}, {"arrows-circle-symbolic"},
{0, {N_("_Copy")}, {}, {{Qt::Key_C, kCtrl}, {Qt::Key_Insert, kCtrl}}, {}},
{0, {N_("Move to _Trash")}, {}, {{Qt::Key_Delete}}, {}},
{kMenu, {N_("_Reload")}, {"arrows-circle-symbolic"},
{{Qt::Key_F5}, {Qt::Key_R}, {Qt::Key_R, kCtrl}}, {}},
};
// clang-format on
@@ -302,7 +312,7 @@ constexpr Action kViewerKeys[] = {
};
// clang-format off
const MenuNode kFileMenu = MenuNode::group("_File", {
const MenuNode kFileMenu = MenuNode::group(N_("_File"), {
MenuNode::item(Action::NewWindow),
MenuNode::item(Action::CloseWindow),
{},
@@ -313,7 +323,7 @@ const MenuNode kFileMenu = MenuNode::group("_File", {
MenuNode::item(Action::Quit),
});
const MenuNode kHelpMenu = MenuNode::group("_Help", {
const MenuNode kHelpMenu = MenuNode::group(N_("_Help"), {
MenuNode::item(Action::Help),
MenuNode::item(Action::Shortcuts),
MenuNode::item(Action::About),
@@ -321,7 +331,7 @@ const MenuNode kHelpMenu = MenuNode::group("_Help", {
const MenuNode kBrowserMenu[] = {
kFileMenu,
MenuNode::group("_Go", {
MenuNode::group(N_("_Go"), {
MenuNode::item(Action::Back),
MenuNode::item(Action::Forward),
MenuNode::item(Action::Location),
@@ -331,7 +341,7 @@ const MenuNode kBrowserMenu[] = {
MenuNode::item(Action::DirParent),
MenuNode::item(Action::DirHome),
}),
MenuNode::group("_View", {
MenuNode::group(N_("_View"), {
MenuNode::item(Action::Sidebar),
{},
MenuNode::item(Action::ThumbPlus),
@@ -358,7 +368,7 @@ const MenuNode kBrowserMenu[] = {
const MenuNode kViewerMenu[] = {
kFileMenu,
MenuNode::group("_Go", {
MenuNode::group(N_("_Go"), {
MenuNode::item(Action::Back),
MenuNode::item(Action::Forward),
MenuNode::item(Action::Location),
@@ -367,7 +377,7 @@ const MenuNode kViewerMenu[] = {
MenuNode::item(Action::PrevFile),
MenuNode::item(Action::NextFile),
}),
MenuNode::group("_View", {
MenuNode::group(N_("_View"), {
MenuNode::item(Action::Information),
{},
MenuNode::item(Action::ZoomIn),
@@ -389,7 +399,7 @@ const MenuNode kViewerMenu[] = {
MenuNode::item(Action::DarkMode),
MenuNode::item(Action::Fullscreen),
}),
MenuNode::group("_Image", {
MenuNode::group(N_("_Image"), {
MenuNode::item(Action::RotateLeft),
MenuNode::item(Action::Mirror),
MenuNode::item(Action::RotateRight),
@@ -469,15 +479,18 @@ accel_label(const ActionDef &def)
return accel_key_label(def.keys[0]);
}
// Where source strings become what the user reads, and so where they get
// translated; call sites mark their literals with N_(). An empty msgid
// would only ever return the catalogue's own header.
QString
menu_label(const char *label, int *mnemonic_index)
{
if (mnemonic_index)
*mnemonic_index = -1;
if (!label)
if (!label || !*label)
return {};
QString s = QString::fromUtf8(label);
QString s = QString::fromUtf8(_(label));
// On macOS, the Alt/Option key modifies characters, so mnemonics are
// unusable for window navigation. The remaining thing mnemonics could do
// is act as unmodified accelerators in menus, though AppKit menu navigation
@@ -527,10 +540,13 @@ static const MenuNode kCropJpegMenu[] = {kFileMenu, kHelpMenu};
static const MenuNode kCommanderMenu[] = {kFileMenu, kHelpMenu};
static constexpr ModeDef kModes[] = {
{"view", "Dawn", kViewerMenu, kViewerKeys},
{"browse", "Dawn", kBrowserMenu, kBrowserKeys},
{"cropjpeg", "Dawn JPEG Cropper", kCropJpegMenu, {}},
{"commander", "Dawn Commander", kCommanderMenu, {}},
// TRANSLATORS: The application's name, in window titles. Transliterate
// it if that is what your script does with foreign names; do not
// translate the word.
{"view", N_("Dawn"), kViewerMenu, kViewerKeys},
{"browse", N_("Dawn"), kBrowserMenu, kBrowserKeys},
{"cropjpeg", N_("Dawn JPEG Cropper"), kCropJpegMenu, {}},
{"commander", N_("Dawn Commander"), kCommanderMenu, {}},
};
static_assert(size(kModes) == size_t(Mode::Count));
+15 -5
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "app-menu-macos.hpp"
#include "action.hpp"
@@ -113,6 +115,12 @@ ns_equiv(dn::Accel a, NSEventModifierFlags *mods)
return [NSString stringWithCharacters:&c length:1];
}
static NSString *
localized(const char *text)
{
return QString::fromUtf8(_(text)).toNSString();
}
static const dn::MenuNode *
find_section(span<const dn::MenuNode> tree, NSString *title)
{
@@ -285,20 +293,22 @@ add_top_menu(NSMenu *main, NSString *title, NSMenu *sub)
static void
add_window_menu(NSMenu *main)
{
NSString *title = @"Window";
// TRANSLATORS: AppKit's standard Window menu, which it does not hand out
// translated; use the wording other applications on the system show.
NSString *title = localized(N_("Window"));
if (has_menu(main, title))
return;
NSMenu *sub = [[[NSMenu alloc] initWithTitle:title] autorelease];
// With no target, AppKit handles these through the responder chain.
[sub addItemWithTitle:@"Minimize"
[sub addItemWithTitle:localized(N_("Minimize"))
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
[sub addItemWithTitle:@"Zoom"
[sub addItemWithTitle:localized(N_("Zoom"))
action:@selector(performZoom:)
keyEquivalent:@""];
[sub addItem:[NSMenuItem separatorItem]];
[sub addItemWithTitle:@"Bring All to Front"
[sub addItemWithTitle:localized(N_("Bring All to Front"))
action:@selector(arrangeInFront:)
keyEquivalent:@""];
add_top_menu(main, title, sub);
@@ -379,7 +389,7 @@ install_macos_app_menu(App *app)
consider(mode.menu);
for (const QString &t : titles) {
// Both trees end with Help, and macOS wants Window just before it.
if (t == QStringLiteral("Help"))
if (&t == &titles.back())
add_window_menu(main);
NSString *title = t.toNSString();
+5 -1
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "app.hpp"
@@ -171,8 +172,11 @@ merge_loaders(const vector<string> &order, const vector<string> &disabled)
return;
used[i] = true;
// libdn only states what the text is; showing it is up to us.
const char *formats = known[i].formats;
out.push_back({QString::fromUtf8(known[i].name),
QString::fromUtf8(known[i].formats),
QString::fromUtf8(formats ? _(formats) : nullptr),
find(disabled.begin(), disabled.end(), known[i].name) ==
disabled.end()});
};
+8 -5
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "kit-browser.hpp"
#include "action.hpp"
@@ -2015,11 +2017,11 @@ scan_dir(Browser &b)
narrow(get_drive_label(drive)).c_str(), get_drive_icon(letter));
}
#else
push_place(b, root, "/", "Computer", "computer-symbolic");
push_place(b, root, "/", _("Computer"), "computer-symbolic");
#endif
push_place(
b, root, QDir::homePath().toStdString(), "Home", "go-home-symbolic");
b, root, QDir::homePath().toStdString(), _("Home"), "go-home-symbolic");
{
const QString pictures =
QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
@@ -2241,7 +2243,7 @@ make_item(Browser &b, const ToolbarSpec &spec)
auto e = make_unique<Entry>();
b.search_ = e.get();
e->flat = true;
e->placeholder = QStringLiteral("Filter");
e->placeholder = QString::fromUtf8(_("Filter"));
e->on_change = [&b](Kit &) {
scan_dir(b);
enqueue_thumbs(b);
@@ -2265,8 +2267,9 @@ make_item(Browser &b, const ToolbarSpec &spec)
n->focus_on_press = false;
n->action = spec.action;
n->pad_x = 2.f;
n->text = spec.action == Action::SortTime ? QStringLiteral("Time")
: QStringLiteral("Name");
// TRANSLATORS: Column headings the browser sorts by, kept short.
const char *by = spec.action == Action::SortTime ? _("Time") : _("Name");
n->text = QString::fromUtf8(by);
return n;
}
+52 -50
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "assoc.hpp"
#include "kit-browser.hpp"
@@ -90,35 +91,35 @@ ContextMenu::fill_items(Kit &kit, const QUrl &url)
add_apps(rec);
add_apps(fall);
auto *new_win = add_item_with_mnemonic("Open in New _Window");
auto *new_win = add_item_with_mnemonic(N_("Open in New _Window"));
new_win->on_click = [this, url](Kit &) {
if (this->on_new_window)
this->on_new_window(url);
};
if (apps_sep) {
add_item_with_mnemonic("Open _With")->sub = apps.get();
add_item_with_mnemonic(N_("Open _With"))->sub = apps.get();
this->subs_.push_back(std::move(apps));
}
if (QFileInfo(path).isDir() && this->on_bookmarked &&
this->on_toggle_bookmark) {
add_sep();
auto *bookmark = add_item_with_mnemonic(this->on_bookmarked(url)
? "Remove from _Bookmarks"
: "Add to _Bookmarks");
? N_("Remove from _Bookmarks")
: N_("Add to _Bookmarks"));
bookmark->on_click = [this, url](Kit &) {
if (this->on_toggle_bookmark)
this->on_toggle_bookmark(url);
};
}
add_sep();
auto *copy = add_item_with_mnemonic("_Copy");
auto *copy = add_item_with_mnemonic(N_("_Copy"));
copy->accel = action_accel(action_def(Action::Copy));
copy->on_click = [url](Kit &) {
const QUrl urls[] = {url};
copy_files(urls, false);
};
if (QFileInfo(path).isFile() && this->on_trash) {
auto *trash = add_item_with_mnemonic("Move to _Trash");
auto *trash = add_item_with_mnemonic(N_("Move to _Trash"));
trash->accel = action_accel(action_def(Action::Trash));
trash->on_click = [this, url](Kit &) {
if (this->on_trash)
@@ -155,20 +156,20 @@ ContextMenu::show(Kit &kit, const QUrl &url, Rect anchor, bool kbd)
constexpr float kDialogActionPad = 16.f;
static unique_ptr<Label>
dialog_label(const QString &text, bool bold = false, bool wrap = false)
dialog_label(const char *text, bool bold = false, bool wrap = false)
{
auto label = make_unique<Label>();
label->text = menu_label(text.toStdString().c_str(), &label->mnemonic);
label->text = menu_label(text, &label->mnemonic);
label->bold = bold;
label->wrap = wrap;
return label;
}
static unique_ptr<Button>
dialog_action(const QString &text, function<void(Kit &)> on_click)
dialog_action(const char *text, function<void(Kit &)> on_click)
{
auto button = make_unique<Button>();
button->text = menu_label(text.toStdString().c_str(), &button->mnemonic);
button->text = menu_label(text, &button->mnemonic);
button->pad_x = kDialogActionPad;
button->on_click = std::move(on_click);
return button;
@@ -178,7 +179,7 @@ static unique_ptr<Button>
dialog_close_action(Dialog &dialog)
{
return dialog_action(
QStringLiteral("_Close"), [&dialog](Kit &kit) { dialog.close(kit); });
N_("_Close"), [&dialog](Kit &kit) { dialog.close(kit); });
}
static QString
@@ -220,10 +221,11 @@ shortcut_row(const ActionDef &def, float accel_w)
{
auto row = make_unique<Row>();
row->gap = 8.f;
auto accel = dialog_label(shortcut_accel(def));
auto accel = make_unique<Label>();
accel->text = shortcut_accel(def);
accel->min_w = accel_w;
accel->dim = true;
auto name = dialog_label(menu_label(def.label[0], nullptr));
auto name = dialog_label(def.label[0]);
row->add_child(std::move(accel), size_t(-1));
row->add_child(std::move(name), size_t(-1));
return row;
@@ -236,10 +238,9 @@ dialog_about(Kit &kit, Dialog &dialog)
{
auto col = make_unique<Column>();
col->gap = 8.f;
col->add_child(dialog_label(QStringLiteral(DAWN_NAME), true), size_t(-1));
col->add_child(
dialog_label(QStringLiteral("Colour-managed image browser and viewer."),
false, true),
col->add_child(dialog_label(DAWN_NAME, true), size_t(-1));
col->add_child(dialog_label(N_("Colour-managed image browser and viewer."),
false, true),
size_t(-1));
dialog.show(kit, std::move(col), 360.f, dialog_close_action(dialog));
}
@@ -250,8 +251,7 @@ dialog_location(
{
auto col = make_unique<Column>();
col->gap = 8.f;
col->add_child(
dialog_label(QStringLiteral("Enter location"), true), size_t(-1));
col->add_child(dialog_label(N_("Enter location"), true), size_t(-1));
auto entry = make_unique<Entry>();
Entry *field = entry.get();
@@ -268,9 +268,8 @@ dialog_location(
auto actions = make_unique<Row>();
actions->gap = 8.f;
actions->add_child(
dialog_action(QStringLiteral("_Open"), submit), size_t(-1));
actions->add_child(dialog_action(QStringLiteral("_Cancel"),
actions->add_child(dialog_action(N_("_Open"), submit), size_t(-1));
actions->add_child(dialog_action(N_("_Cancel"),
[&dialog](Kit &inner) { dialog.close(inner); }),
size_t(-1));
dialog.show(kit, std::move(col), 420.f, std::move(actions));
@@ -312,8 +311,7 @@ dialog_shortcuts(Kit &kit, Dialog &dialog, span<const MenuNode> tree,
auto col = make_unique<Column>();
col->gap = 2.f;
col->add_child(
dialog_label(QStringLiteral("Keyboard Shortcuts"), true), size_t(-1));
col->add_child(dialog_label(N_("Keyboard Shortcuts"), true), size_t(-1));
for (const MenuNode &section : tree) {
if (section.items.empty())
continue;
@@ -324,8 +322,7 @@ dialog_shortcuts(Kit &kit, Dialog &dialog, span<const MenuNode> tree,
});
if (!any)
continue;
col->add_child(
dialog_label(menu_label(section.title, nullptr), true), size_t(-1));
col->add_child(dialog_label(section.title, true), size_t(-1));
for_leaves(section.items, [&](Action action) {
const ActionDef &def = action_def(action);
if (!has_shortcut(def))
@@ -343,8 +340,7 @@ dialog_shortcuts(Kit &kit, Dialog &dialog, span<const MenuNode> tree,
if ((def.flags & ActionInMenu) || !has_shortcut(def))
return;
if (!other) {
col->add_child(
dialog_label(QStringLiteral("Other"), true), size_t(-1));
col->add_child(dialog_label(N_("Other"), true), size_t(-1));
other = true;
}
seen[i] = true;
@@ -363,8 +359,8 @@ dialog_shortcuts(Kit &kit, Dialog &dialog, span<const MenuNode> tree,
// --- Settings dialog ---------------------------------------------------------
// TODO(p): This needs to be in one place with sizes.
static const QString kThumbSizeNames[] = {QStringLiteral("Small"),
QStringLiteral("Normal"), QStringLiteral("Large"), QStringLiteral("Huge")};
static const char *const kThumbSizeNames[] = {
N_("Small"), N_("Normal"), N_("Large"), N_("Huge")};
static QString
loader_text(const SettingsDraft::Loader &loader)
@@ -377,7 +373,7 @@ loader_text(const SettingsDraft::Loader &loader)
// TODO(p): This sizing model is bad.
static unique_ptr<Row>
settings_row(const QString &label, float label_w, unique_ptr<Widget> control)
settings_row(const char *label, float label_w, unique_ptr<Widget> control)
{
auto text = dialog_label(label);
text->min_w = label_w;
@@ -467,19 +463,23 @@ dialog_settings(Kit &kit, Dialog &dialog, SettingsDraft draft,
// Save hands that copy back, and Cancel simply drops it.
auto state = make_shared<SettingsDraft>(std::move(draft));
// The gutter is as wide as the labels that go in it, which is only known
// once they have been translated.
float label_w = 0;
const QString thumb_label = QStringLiteral("Default _thumbnail size");
const QString icc_label = QStringLiteral("ICC _profile override");
for (const QString &label : {thumb_label, icc_label})
label_w = max(label_w, kit.pts(kit.text_width(label, false)));
const char *const thumb_label = N_("Default _thumbnail size");
const char *const icc_label = N_("ICC _profile override");
for (const char *label : {thumb_label, icc_label}) {
label_w = max(label_w,
kit.pts(kit.text_width(menu_label(label, nullptr), false)));
}
auto col = make_unique<Column>();
col->gap = 4.f;
col->add_child(dialog_label(QStringLiteral("Settings"), true), size_t(-1));
col->add_child(dialog_label(N_("Settings"), true), size_t(-1));
auto combo = make_unique<Combo>();
for (const QString &name : kThumbSizeNames)
combo->items.push_back(name);
for (const char *name : kThumbSizeNames)
combo->items.push_back(QString::fromUtf8(_(name)));
for (int i = 0; i < int(size(kThumbSizes)); i++) {
if (kThumbSizes[i] == state->thumbnail_size)
combo->current = i;
@@ -491,16 +491,17 @@ dialog_settings(Kit &kit, Dialog &dialog, SettingsDraft draft,
settings_row(thumb_label, label_w, std::move(combo)), size_t(-1));
auto names =
settings_check("Show _filenames by default", state->show_filenames);
settings_check(N_("Show _filenames by default"), state->show_filenames);
Checkbox *names_ref = names.get();
names->on_click = [state, names_ref](Kit &) {
state->show_filenames = names_ref->checked;
};
col->add_child(settings_row({}, label_w, std::move(names)), size_t(-1));
col->add_child(
settings_row(nullptr, label_w, std::move(names)), size_t(-1));
auto entry = make_unique<Entry>();
entry->text = state->icc_profile_path;
entry->placeholder = QStringLiteral("Path to an ICC profile");
entry->placeholder = QString::fromUtf8(_("Path to an ICC profile"));
Entry *entry_ref = entry.get();
entry->on_change = [state, entry_ref](Kit &) {
state->icc_profile_path = entry_ref->text;
@@ -509,18 +510,18 @@ dialog_settings(Kit &kit, Dialog &dialog, SettingsDraft draft,
settings_row(icc_label, label_w, std::move(entry)), size_t(-1));
auto dither = settings_check(
"Disable _dithering on 8-bit swapchains", state->disable_dithering);
N_("Disable _dithering on 8-bit swapchains"), state->disable_dithering);
Checkbox *dither_ref = dither.get();
dither->on_click = [state, dither_ref](Kit &) {
state->disable_dithering = dither_ref->checked;
};
col->add_child(settings_row({}, label_w, std::move(dither)), size_t(-1));
col->add_child(
settings_row(nullptr, label_w, std::move(dither)), size_t(-1));
col->add_child(make_unique<Sep>(), size_t(-1));
auto note = dialog_label(
QStringLiteral("Image loaders may be able to handle multiple "
"formats. Failures pass through."),
auto note = dialog_label(N_("Image loaders may be able to handle multiple "
"formats. Failures pass through."),
false, true);
note->dim = true;
col->add_child(std::move(note), size_t(-1));
@@ -533,9 +534,10 @@ dialog_settings(Kit &kit, Dialog &dialog, SettingsDraft draft,
loaders->gap = 2.f;
rows->col = loaders.get();
for (int i = 0; i < int(state->loaders.size()); i++) {
auto up = loader_arrow("go-up-symbolic", QStringLiteral("Move up"));
auto up =
loader_arrow("go-up-symbolic", QString::fromUtf8(_("Move up")));
auto down =
loader_arrow("go-down-symbolic", QStringLiteral("Move down"));
loader_arrow("go-down-symbolic", QString::fromUtf8(_("Move down")));
auto check = make_unique<Checkbox>();
check->grow = true;
check->wrap = true;
@@ -573,14 +575,14 @@ dialog_settings(Kit &kit, Dialog &dialog, SettingsDraft draft,
auto actions = make_unique<Row>();
actions->gap = 8.f;
actions->add_child(
dialog_action(QStringLiteral("_Save"),
dialog_action(N_("_Save"),
[&dialog, state, on_save = std::move(on_save)](Kit &inner) {
dialog.close(inner);
if (on_save)
on_save(*state);
}),
size_t(-1));
actions->add_child(dialog_action(QStringLiteral("_Cancel"),
actions->add_child(dialog_action(N_("_Cancel"),
[&dialog](Kit &inner) { dialog.close(inner); }),
size_t(-1));
dialog.show(kit, std::move(col), 520.f, std::move(actions));
+26 -11
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "kit-cie-diagram.hpp"
#include "cmf-cie1931-2deg-1nm.h"
@@ -35,13 +37,26 @@ constexpr Colour kMidGreyCol{188 / 255.f, 188 / 255.f, 188 / 255.f, 1.f};
constexpr Colour kBlackCol{0.f, 0.f, 0.f, 1.f};
constexpr Colour kWhiteCol{1.f, 1.f, 1.f, 1.f};
const QString kSourceLab = QStringLiteral("Source");
const QString kTargetLab = QStringLiteral("Target");
// Static initialisation would run before the catalogue has been bound,
// and the language cannot change afterwards, so once on use is enough.
static const QString &
source_label()
{
static const QString text = QString::fromUtf8(_("Source"));
return text;
}
static const QString &
target_label()
{
static const QString text = QString::fromUtf8(_("Target"));
return text;
}
static int
caption_h(const Kit &kit)
{
return kit.px(kCapGap) + kit.text_height(kSourceLab, 0, false) +
return kit.px(kCapGap) + kit.text_height(source_label(), 0, false) +
kit.px(4.f);
}
@@ -233,8 +248,8 @@ CieDiagram::measure_content(Kit &kit, int max_w, int max_h)
const int cap = caption_h(kit);
const int plot_h = max(0, max_h - cap);
const Rect fit = plot_rect({0, 0, max_w, plot_h});
const int labs = kit.text_width(kSourceLab, false) + kit.px(8.f) +
kit.text_width(kTargetLab, false);
const int labs = kit.text_width(source_label(), false) + kit.px(8.f) +
kit.text_width(target_label(), false);
return {max(fit.w, labs), fit.h + cap};
}
@@ -247,8 +262,8 @@ CieDiagram::arrange_content(Kit &kit, Rect alloc)
void
CieDiagram::prepare(Kit &kit)
{
kit.cache_text(kSourceLab, false);
kit.cache_text(kTargetLab, false);
kit.cache_text(source_label(), false);
kit.cache_text(target_label(), false);
const int cap = caption_h(kit);
const Rect plot =
@@ -290,7 +305,7 @@ CieDiagram::paint(Kit &kit) const
const int x0 = plot.x > 0 ? plot.x : this->r.x;
const int cap_y0 = plot.h >= 8 ? plot.y + plot.h : this->r.y;
const int y = cap_y0 + kit.px(kCapGap);
const int th = kit.text_height(kSourceLab, 0, true);
const int th = kit.text_height(source_label(), 0, true);
const int cap_w = plot.w >= 8 ? plot.w : this->r.w;
kit.list_.add_rect_filled({x0, y, x0 + cap_w, y + th}, kMidGreyCol);
if (plot.w >= 8 && plot.h >= 8 && !this->slot_.empty())
@@ -299,10 +314,10 @@ CieDiagram::paint(Kit &kit) const
const int cx = x0 + cap_w / 2;
const int gap = kit.px(4.f);
const int widthS = kit.text_width(kSourceLab, true);
const int widthS = kit.text_width(source_label(), true);
kit.emit_text(
float(cx - gap - widthS), float(y), kSourceLab, kBlackCol, true);
kit.emit_text(float(cx + gap), float(y), kTargetLab, kWhiteCol, true);
float(cx - gap - widthS), float(y), source_label(), kBlackCol, true);
kit.emit_text(float(cx + gap), float(y), target_label(), kWhiteCol, true);
}
} // namespace dn
+21 -18
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "kit-viewer.hpp"
@@ -604,8 +605,15 @@ static unique_ptr<Sidebar>
make_sidebar(Viewer &v)
{
// FIXME: This needs proper layouting.
const float label_w =
float(v.kit_.text_width(QStringLiteral("Height:"), true)) / v.kit_.dpr_;
// The widest of them decides, and only translation says which that is.
const char *const labels[] = {
N_("Name:"), N_("Loader:"), N_("Width:"), N_("Height:")};
float label_w = 0;
for (const char *label : labels) {
label_w = max(label_w,
float(v.kit_.text_width(QString::fromUtf8(_(label)), true)) /
v.kit_.dpr_);
}
auto col = make_unique<ScrollColumn>();
v.info_ = col.get();
@@ -614,24 +622,19 @@ make_sidebar(Viewer &v)
col->pad_y = kWinPadX * 2.f;
col->grow = true;
col->add_child(meta_row(QStringLiteral("Name:"), QStringLiteral("-"),
label_w, v.name_label_),
size_t(-1));
col->add_child(meta_row(QStringLiteral("Loader:"), QStringLiteral("-"),
label_w, v.loader_label_),
size_t(-1));
col->add_child(meta_row(QStringLiteral("Width:"), QStringLiteral("-"),
label_w, v.width_label_),
size_t(-1));
col->add_child(meta_row(QStringLiteral("Height:"), QStringLiteral("-"),
label_w, v.height_label_),
size_t(-1));
Label **const values[] = {
&v.name_label_, &v.loader_label_, &v.width_label_, &v.height_label_};
for (size_t i = 0; i < size(labels); i++) {
col->add_child(meta_row(QString::fromUtf8(_(labels[i])),
QStringLiteral("-"), label_w, *values[i]),
size_t(-1));
}
#if DAWN_WITH_JPEG_QS
// QuantSmooth processing can take extremely long,
// so it's not elligible as a regular loader.
auto jpegqs = make_unique<Checkbox>();
jpegqs->text = QStringLiteral("Enable JPEG Quant Smooth");
jpegqs->text = QString::fromUtf8(_("Enable JPEG Quant Smooth"));
jpegqs->enabled_ = false;
jpegqs->on_click = [&v](Kit &) {
if (!v.jpeg_quant_smooth_)
@@ -649,7 +652,7 @@ make_sidebar(Viewer &v)
#endif
auto exiftool = make_unique<Button>();
exiftool->text = QStringLiteral("Launch ExifTool");
exiftool->text = QString::fromUtf8(_("Launch ExifTool"));
exiftool->on_click = [&v](Kit &) {
if (v.page_ && v.page_->host && v.page_->host->launch_exiftool)
v.page_->host->launch_exiftool(v.url_);
@@ -681,8 +684,8 @@ sync_ui(Viewer &v, Page &ui)
v.error_->invalidate_measure();
}
if (v.error_->visible && v.error_label_)
v.error_label_->set_text(
QStringLiteral("Error: ") + QString::fromStdString(v.message_));
v.error_label_->set_text(QString::fromUtf8(_("Error: %1"))
.arg(QString::fromStdString(v.message_)));
}
if (v.info_)
fill_info_texts(v, v.current_ ? v.current_.get() : v.image_.get());
+2 -2
View File
@@ -3084,10 +3084,10 @@ Menu::add_item(const QString &text)
}
MenuItem *
Menu::add_item_with_mnemonic(const QString &text)
Menu::add_item_with_mnemonic(const char *label)
{
auto item = make_unique<MenuItem>();
item->text = menu_label(text.toStdString().c_str(), &item->mnemonic);
item->text = menu_label(label, &item->mnemonic);
MenuItem *ref = item.get();
if (this->col)
this->col->add_child(std::move(item), size_t(-1));
+1 -1
View File
@@ -647,7 +647,7 @@ struct Menu : MenuPopup {
void build(Kit &kit, std::span<const MenuNode> nodes, const Actor &actor);
void sync();
MenuItem *add_item(const QString &text);
MenuItem *add_item_with_mnemonic(const QString &text);
MenuItem *add_item_with_mnemonic(const char *label);
void add_sep();
void clear(Kit &kit);
Size measure_content(Kit &kit, int max_w, int max_h) override;
+53 -22
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "app.hpp"
#include "libdn/libdn.h"
@@ -28,6 +29,9 @@
#include <QCoreApplication>
#include <QDir>
#include <QGuiApplication>
#include <QLibraryInfo>
#include <QLocale>
#include <QTranslator>
#include <QUrl>
#include <QtLogging>
@@ -68,15 +72,15 @@ error_fallback(dawn::ipc::ErrorCode code)
using dawn::ipc::ErrorCode;
switch (code) {
case ErrorCode::NotFound:
return "not found";
return _("not found");
case ErrorCode::PermissionDenied:
return "permission denied";
return _("permission denied");
case ErrorCode::InvalidArgument:
return "invalid argument";
return _("invalid argument");
case ErrorCode::Internal:
return "internal error";
return _("internal error");
default:
return "open failed";
return _("open failed");
}
}
@@ -122,11 +126,11 @@ try_remote_open(const QString &session, const vector<QUrl> &urls, dn::Mode mode,
const char *mismatch = nullptr;
if (status == HelloStatus::VersionMismatch)
mismatch = "version";
mismatch = N_("running isolated (version mismatch)");
else if (status == HelloStatus::SessionMismatch)
mismatch = "session";
mismatch = N_("running isolated (session mismatch)");
if (mismatch && !reported_mismatch) {
qWarning("running isolated (%s mismatch)", mismatch);
qWarning("%s", _(mismatch));
reported_mismatch = true;
}
return {};
@@ -134,9 +138,28 @@ try_remote_open(const QString &session, const vector<QUrl> &urls, dn::Mode mode,
#endif
// Qt's own text -- the generic command-line options, the items macOS adds to
// the application menu -- lives in its catalogues, which it will not load on
// its own. Qt reads the environment itself, and does not know about LANGUAGE.
static void
install_qt_translations(QCoreApplication &app)
{
static QTranslator translations;
if (translations.isEmpty() &&
!translations.load(QLocale(), QStringLiteral("qtbase"),
QStringLiteral("_"),
QLibraryInfo::path(QLibraryInfo::TranslationsPath)))
return;
app.installTranslator(&translations);
}
int
main(int argc, char **argv)
{
// Before the first translated literal, which the parser below builds.
dawn::gettext_init();
QCoreApplication::setApplicationName(QStringLiteral("dn"));
QCoreApplication::setApplicationVersion(QStringLiteral(DAWN_VERSION));
QGuiApplication::setDesktopFileName(QStringLiteral(DAWN_NAMESPACE));
@@ -148,42 +171,46 @@ main(int argc, char **argv)
QCommandLineParser parser;
parser.setApplicationDescription(
QStringLiteral("Display images or browse directories."));
QString::fromUtf8(_("Display images or browse directories.")));
parser.addHelpOption();
parser.addVersionOption();
// Option names are what the user types, and stay as they are.
const QCommandLineOption new_instance_opt(QStringLiteral("new-instance"),
QStringLiteral("Do not connect to a running dn; start a new process."));
QString::fromUtf8(
_("Do not connect to a running dn; start a new process.")));
parser.addOption(new_instance_opt);
const QCommandLineOption invalidate_opt(QStringLiteral("invalidate-cache"),
QStringLiteral("Remove invalid wide thumbnails and exit."));
QString::fromUtf8(_("Remove invalid wide thumbnails and exit.")));
parser.addOption(invalidate_opt);
const QCommandLineOption mode_opt(QStringLiteral("mode"),
QStringLiteral("Application: view, browse, cropjpeg, commander."),
QString::fromUtf8(_("Application: view, browse, cropjpeg, commander.")),
QStringLiteral("mode"));
parser.addOption(mode_opt);
const QCommandLineOption list_supported_opt(
QStringLiteral("list-supported-media-types"),
QStringLiteral("Output supported media types and exit."));
QString::fromUtf8(_("Output supported media types and exit.")));
parser.addOption(list_supported_opt);
const QCommandLineOption list_extensions_opt(
QStringLiteral("list-supported-extensions"),
QStringLiteral("Output supported filename globs and exit."));
QString::fromUtf8(_("Output supported filename globs and exit.")));
parser.addOption(list_extensions_opt);
parser.addPositionalArgument(QStringLiteral("path | URL"),
QStringLiteral(
"Image file or directory. Repeat to open multiple windows. "
"Defaults to an empty cropper or the current directory."),
QStringLiteral("[path | URL]..."));
// TRANSLATORS: The name of the positional argument, and its syntax.
parser.addPositionalArgument(QString::fromUtf8(_("path | URL")),
QString::fromUtf8(
_("Image file or directory. Repeat to open multiple windows. "
"Defaults to an empty cropper or the current directory.")),
QString::fromUtf8(_("[path | URL]...")));
{
// xdg_data_dirs() invokes the static QCoreApplication::instance().
QCoreApplication bootstrap(argc, argv);
install_qt_translations(bootstrap);
parser.process(bootstrap);
if (parser.isSet(invalidate_opt)) {
@@ -207,20 +234,24 @@ main(int argc, char **argv)
if (parser.isSet(mode_opt)) {
auto parsed = dn::parse_mode(parser.value(mode_opt).toStdString());
if (!parsed) {
qWarning(
"unknown mode: %s", qUtf8Printable(parser.value(mode_opt)));
qWarning("%s",
qUtf8Printable(QString::fromUtf8(_("unknown mode: %1"))
.arg(parser.value(mode_opt))));
return EXIT_FAILURE;
}
mode = *parsed;
}
#if !DAWN_WIP
if (!dn::viewer_mode(mode)) {
qWarning("unsupported mode: %s", dn::mode_def(mode).name);
qWarning("%s",
qUtf8Printable(QString::fromUtf8(_("unsupported mode: %1"))
.arg(QLatin1String(dn::mode_def(mode).name))));
return EXIT_FAILURE;
}
#endif
dn::App app(argc, argv, mode);
install_qt_translations(app);
QStringList raw = parser.positionalArguments();
const bool bare = raw.isEmpty();
const QString cwd = QDir::currentPath();
+21 -15
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "window.hpp"
@@ -507,14 +508,15 @@ Window::show_help()
const QString path = help_document_path();
if (!QFile::exists(path)) {
const QString message =
QStringLiteral("Help document not found: ") + path;
QString::fromUtf8(_("Help document not found: %1")).arg(path);
qWarning("%s", qUtf8Printable(message));
show_viewer_error(message);
return;
}
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path))) {
const QString message =
QStringLiteral("Could not open the help document: ") + path;
QString::fromUtf8(_("Could not open the help document: %1"))
.arg(path);
qWarning("%s", qUtf8Printable(message));
show_viewer_error(message);
}
@@ -537,9 +539,9 @@ Window::launch_exiftool(const QUrl &url)
auto *report = new QTemporaryFile(
QDir::tempPath() + QStringLiteral("/dn-exiftool-XXXXXX.txt"), qGuiApp);
if (!report->open()) {
show_error(
QStringLiteral("Could not create a temporary ExifTool report: ") +
report->errorString());
show_error(QString::fromUtf8(
_("Could not create a temporary ExifTool report: %1"))
.arg(report->errorString()));
report->deleteLater();
return;
}
@@ -569,8 +571,8 @@ Window::launch_exiftool(const QUrl &url)
if (error != QProcess::FailedToStart || *finished)
return;
*finished = true;
show_error(QStringLiteral("Could not launch ExifTool: ") +
process->errorString());
show_error(QString::fromUtf8(_("Could not launch ExifTool: %1"))
.arg(process->errorString()));
report->deleteLater();
process->deleteLater();
});
@@ -583,20 +585,24 @@ Window::launch_exiftool(const QUrl &url)
QFile output(report_path);
if (output.open(QIODeviceBase::Append | QIODeviceBase::Text)) {
if (exit_status == QProcess::CrashExit) {
output.write("\nExifTool terminated abnormally.\n");
output.write(QByteArrayLiteral("\n") +
_("ExifTool terminated abnormally.") + "\n");
} else if (exit_code != 0) {
output.write(
QStringLiteral("\nExifTool exited with status %1.\n")
.arg(exit_code)
output.write(QStringLiteral("\n%1\n")
.arg(QString::fromUtf8(
_("ExifTool exited with status %1."))
.arg(exit_code))
.toUtf8());
} else if (output.size() == 0) {
output.write("ExifTool produced no output.\n");
output.write(
QByteArray(_("ExifTool produced no output.")) + "\n");
}
output.close();
}
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(report_path)))
show_error(QStringLiteral("Could not open the ExifTool report "
"through a .txt association."));
show_error(QString::fromUtf8(_("Could not open the ExifTool "
"report through a .txt "
"association.")));
process->deleteLater();
});
process->start();
@@ -618,7 +624,7 @@ void
Window::sync_title()
{
const QUrl url = current_url();
const QString app = QString::fromUtf8(mode_def(this->mode_).title);
const QString app = QString::fromUtf8(_(mode_def(this->mode_).title));
const QString title = url.isEmpty()
? app
: url_parse_name(url) + QStringLiteral(" \u2014 ") + app;
+6
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "ipc-rpc.hpp"
#include "ipc-shm.hpp"
#include "ipc/imaged.lxdr.hpp"
@@ -183,6 +185,10 @@ handle_payload(State &state, uint64_t connection, span<const byte> payload,
int
main()
{
// The client displays what we send it, already rendered, so these
// diagnostics speak whatever language this process started in.
dawn::gettext_init();
auto endpoint = dawn::ipc::Endpoint::listen(kService);
if (endpoint.status != dawn::ipc::Endpoint::ListenStatus::Ok) {
fprintf(stderr, "dnimaged: %s\n",
+6
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "ipc-rpc.hpp"
#include "ipc-shm.hpp"
#include "ipc/thumbd.lxdr.hpp"
@@ -172,6 +174,10 @@ handle_payload(State &state, uint64_t connection, span<const byte> payload,
int
main()
{
// The client displays what we send it, already rendered, so these
// diagnostics speak whatever language this process started in.
dawn::gettext_init();
auto endpoint = dawn::ipc::Endpoint::listen(kService);
if (endpoint.status != dawn::ipc::Endpoint::ListenStatus::Ok) {
fprintf(stderr, "dnthumbd: %s\n",
+22 -1
View File
@@ -9,7 +9,7 @@ package names:
.Required dependencies
```sh
apt install build-essential cmake qt6-base-dev wayland-protocols glslang-tools \
librsvg2-bin libwebp-dev libjpeg-dev libcolord-dev shared-mime-info
librsvg2-bin libwebp-dev libjpeg-dev libcolord-dev shared-mime-info gettext
```
.Optional dependencies
@@ -54,6 +54,27 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -- package
```
Translations
------------
Message catalogues are compiled by every build, from the PO files committed
in _po/_, and they need nothing but the gettext tools. Rewriting those files
is a separate step, run after the sources have changed:
```sh
cmake --build build --target update-po
```
An underscore within a message marks the mnemonic, the letter that follows
it; translations keep one, on a letter that nothing else in the same menu has
taken. Everything else the user reads comes from Qt's own catalogues.
A new language starts from the template that step leaves behind, and is
picked up once its code is added to _po/LINGUAS_:
```sh
(cd po && msginit --locale=cs --input=dawn.pot)
```
Windows
-------
Windows is cross-compiled in the CI pipeline, and while it can theoretically
+2
View File
@@ -9,6 +9,7 @@ add_custom_command(
VERBATIM
)
add_library(libdn SHARED
gettext.cpp
ipc.cpp
libdn.cpp
load-icns.cpp
@@ -23,6 +24,7 @@ find_package(Threads REQUIRED)
target_link_libraries(libdn
PUBLIC
${DAWN_LCMS2_TARGET}
Intl::Intl
Vulkan::Vulkan
PRIVATE
Threads::Threads
+5 -4
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn.h"
@@ -35,7 +36,7 @@ make_cfstring(string_view value, Error *error)
reinterpret_cast<const UInt8 *>(value.data()), CFIndex(value.size()),
kCFStringEncodingUTF8, false);
if (!result)
fail(error, "invalid UTF-8 configuration string");
fail(error, _("invalid UTF-8 configuration string"));
return result;
}
@@ -48,7 +49,7 @@ to_utf8(CFStringRef value, Error *error)
vector<char> bytes(static_cast<size_t>(maximum));
if (!CFStringGetCString(
value, bytes.data(), maximum, kCFStringEncodingUTF8)) {
fail(error, "cannot convert configuration string to UTF-8");
fail(error, _("cannot convert configuration string to UTF-8"));
return nullopt;
}
return string(bytes.data());
@@ -73,7 +74,7 @@ config_get(string_view key, Error *error)
if (CFGetTypeID(value) != CFStringGetTypeID()) {
CFRelease(value);
fail(error, "configuration value is not a string");
fail(error, _("configuration value is not a string"));
return nullopt;
}
optional<string> result = to_utf8(CFStringRef(value), error);
@@ -103,7 +104,7 @@ config_set(string_view key, string_view value, Error *error)
CFRelease(cf_value);
if (!CFPreferencesSynchronize(CFSTR(DAWN_NAMESPACE),
kCFPreferencesCurrentUser, kCFPreferencesAnyHost)) {
fail(error, "cannot synchronize configuration");
fail(error, _("cannot synchronize configuration"));
return false;
}
return true;
+16 -9
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn.h"
@@ -194,7 +195,7 @@ config_path(Error *error)
}
if (const char *home = getenv("HOME"); home && *home)
return fs::path(home) / ".config" / DAWN_NAMESPACE / "dawn.conf";
fail(error, "cannot locate the user configuration directory");
fail(error, _("cannot locate the user configuration directory"));
return {};
}
@@ -204,20 +205,22 @@ load_ini(const fs::path &path, Error *error)
error_code ec;
if (!fs::exists(path, ec)) {
if (ec)
fail(error, "cannot inspect configuration file: " + ec.message());
fail(error,
format_message(_("cannot inspect configuration file: %s"),
ec.message().c_str()));
else
return IniFile{};
return nullopt;
}
ifstream input(path, ios::binary);
if (!input) {
fail(error, "cannot open configuration file");
fail(error, _("cannot open configuration file"));
return nullopt;
}
string text(
(istreambuf_iterator<char>(input)), istreambuf_iterator<char>());
if (input.bad()) {
fail(error, "cannot read configuration file");
fail(error, _("cannot read configuration file"));
return nullopt;
}
return detail::ini_parse(text);
@@ -231,7 +234,7 @@ config_get(string_view key, Error *error)
const auto parts = split_key(key);
if (!parts) {
fail(error, "invalid configuration key");
fail(error, _("invalid configuration key"));
return nullopt;
}
const fs::path path = config_path(error);
@@ -258,7 +261,7 @@ config_set(string_view key, string_view value, Error *error)
const auto parts = split_key(key);
if (!parts) {
fail(error, "invalid configuration key");
fail(error, _("invalid configuration key"));
return false;
}
const fs::path path = config_path(error);
@@ -284,7 +287,9 @@ config_set(string_view key, string_view value, Error *error)
error_code ec;
fs::create_directories(path.parent_path(), ec);
if (ec) {
fail(error, "cannot create configuration directory: " + ec.message());
fail(error,
format_message(_("cannot create configuration directory: %s"),
ec.message().c_str()));
return false;
}
const fs::path temporary = path.string() + ".new";
@@ -293,13 +298,15 @@ config_set(string_view key, string_view value, Error *error)
ofstream output(temporary, ios::binary | ios::trunc);
if (!output || !output.write(data.data(), streamsize(data.size())) ||
!output.flush()) {
fail(error, "cannot write configuration file");
fail(error, _("cannot write configuration file"));
return false;
}
}
fs::rename(temporary, path, ec);
if (ec) {
fail(error, "cannot replace configuration file: " + ec.message());
fail(error,
format_message(_("cannot replace configuration file: %s"),
ec.message().c_str()));
return false;
}
return true;
+13 -12
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn.h"
@@ -29,9 +30,9 @@ fail(Error *error, const char *operation, LSTATUS status = ERROR_SUCCESS)
return;
error->code = Error::Code::Io;
error->message = operation;
if (status != ERROR_SUCCESS)
error->message += ": Windows error " + to_string(status);
error->message = status == ERROR_SUCCESS
? string(operation)
: format_message(_("%s: Windows error %ld"), operation, long(status));
}
static optional<wstring>
@@ -43,14 +44,14 @@ to_wide(string_view value, Error *error)
const int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
value.data(), int(value.size()), nullptr, 0);
if (!size) {
fail(error, "invalid UTF-8 configuration string", GetLastError());
fail(error, _("invalid UTF-8 configuration string"), GetLastError());
return nullopt;
}
wstring out(size_t(size), L'\0');
if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value.data(),
int(value.size()), out.data(), size)) {
fail(error, "cannot convert configuration string", GetLastError());
fail(error, _("cannot convert configuration string"), GetLastError());
return nullopt;
}
return out;
@@ -65,14 +66,14 @@ to_utf8(wstring_view value, Error *error)
const int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
value.data(), int(value.size()), nullptr, 0, nullptr, nullptr);
if (!size) {
fail(error, "invalid UTF-16 configuration string", GetLastError());
fail(error, _("invalid UTF-16 configuration string"), GetLastError());
return nullopt;
}
string out(size_t(size), '\0');
if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value.data(),
int(value.size()), out.data(), size, nullptr, nullptr)) {
fail(error, "cannot convert configuration string", GetLastError());
fail(error, _("cannot convert configuration string"), GetLastError());
return nullopt;
}
return out;
@@ -93,7 +94,7 @@ registry_name(string_view key, Error *error)
{
const size_t slash = key.rfind('/');
if (slash == string_view::npos || slash == 0 || slash + 1 == key.size()) {
fail(error, "invalid configuration key");
fail(error, _("invalid configuration key"));
return nullopt;
}
string subkey = "Software\\" DAWN_NAMESPACE "\\";
@@ -124,7 +125,7 @@ config_get(string_view key, Error *error)
if (status == ERROR_FILE_NOT_FOUND)
return nullopt;
if (status != ERROR_SUCCESS) {
fail(error, "cannot read configuration value", status);
fail(error, _("cannot read configuration value"), status);
return nullopt;
}
@@ -132,7 +133,7 @@ config_get(string_view key, Error *error)
status = RegGetValueW(HKEY_CURRENT_USER, name->subkey.c_str(),
name->value.c_str(), RRF_RT_REG_SZ, nullptr, data.data(), &bytes);
if (status != ERROR_SUCCESS) {
fail(error, "cannot read configuration value", status);
fail(error, _("cannot read configuration value"), status);
return nullopt;
}
@@ -157,7 +158,7 @@ config_set(string_view key, string_view value, Error *error)
LSTATUS status = RegCreateKeyExW(HKEY_CURRENT_USER, name->subkey.c_str(), 0,
nullptr, 0, KEY_SET_VALUE, nullptr, &handle, nullptr);
if (status != ERROR_SUCCESS) {
fail(error, "cannot open configuration key", status);
fail(error, _("cannot open configuration key"), status);
return false;
}
@@ -166,7 +167,7 @@ config_set(string_view key, string_view value, Error *error)
DWORD((data->size() + 1) * sizeof(wchar_t)));
RegCloseKey(handle);
if (status != ERROR_SUCCESS) {
fail(error, "cannot write configuration value", status);
fail(error, _("cannot write configuration value"), status);
return false;
}
return true;
+159
View File
@@ -0,0 +1,159 @@
//
// gettext.cpp: message catalogue startup
//
// Copyright The Dawn Authors
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include <clocale>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <string>
#include <system_error>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <climits>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#else
#include <unistd.h>
#endif
#endif
using namespace std;
namespace dawn
{
#ifdef _WIN32
// Windows packages and macOS bundles are relocatable, so their catalogues can
// only be found relative to the executable, the way the user guide already is.
// The wide path matters here: bindtextdomain() would have to squeeze an
// installation directory through the ANSI codepage first.
static wstring
package_locale_dir()
{
wchar_t buf[MAX_PATH] = {};
const DWORD len = GetModuleFileNameW(nullptr, buf, DWORD(size(buf)));
if (!len || len == size(buf))
return {};
wstring path(buf, len);
const size_t slash = path.find_last_of(L"\\/");
if (slash == wstring::npos)
return {};
path.resize(slash + 1);
return path + L"share\\locale";
}
#else
static string
executable_path()
{
char buf[PATH_MAX] = {};
#ifdef __APPLE__
uint32_t size = sizeof buf;
if (_NSGetExecutablePath(buf, &size))
return {};
return buf;
#else
// Other Unices need their own means of asking, and go without.
const ssize_t len = readlink("/proc/self/exe", buf, sizeof buf - 1);
if (len < 0)
return {};
return string(buf, size_t(len));
#endif
}
// The build tree keeps catalogues beside the binaries, as it does the
// user guide.
static string
package_locale_dir()
{
string path = executable_path();
const size_t slash = path.find_last_of('/');
if (slash == string::npos)
return {};
path.resize(slash + 1);
#ifdef __APPLE__
return path + "../Resources/share/locale";
#else
return path + "../share/locale";
#endif
}
static string
locale_dir()
{
#ifdef __APPLE__
// The bundle is one relocatable thing, installed or not.
return package_locale_dir();
#else
// A Unix installation puts its catalogues wherever the packager said,
// which is what DAWN_LOCALEDIR records; asking the executable instead
// would ignore a CMAKE_INSTALL_LOCALEDIR pointing anywhere else. Only
// an uninstalled build tree has no such directory to find.
error_code ec;
if (filesystem::is_directory(DAWN_LOCALEDIR, ec))
return DAWN_LOCALEDIR;
return package_locale_dir();
#endif
}
#endif
void
gettext_init()
{
// GNU libintl derives the language from the environment on Unix, and from
// the user's interface preferences on macOS and Windows. Both are what
// the user asked for, including a deliberate LC_ALL=C.
setlocale(LC_ALL, "");
// Configuration files, IPC and shader sources are all written and parsed
// C-style; only what the user reads follows the locale.
setlocale(LC_NUMERIC, "C");
#ifdef _WIN32
const wstring dir = package_locale_dir();
if (dir.empty())
bindtextdomain(DAWN_TEXTDOMAIN, DAWN_LOCALEDIR);
else
wbindtextdomain(DAWN_TEXTDOMAIN, dir.c_str());
#else
const string dir = locale_dir();
bindtextdomain(DAWN_TEXTDOMAIN, dir.empty() ? DAWN_LOCALEDIR : dir.c_str());
#endif
bind_textdomain_codeset(DAWN_TEXTDOMAIN, "UTF-8");
}
string
format_message(const char *format, ...)
{
va_list args;
va_start(args, format);
va_list measure;
va_copy(measure, args);
const int len = vsnprintf(nullptr, 0, format, measure);
va_end(measure);
string message;
if (len > 0) {
message.resize(size_t(len));
vsnprintf(message.data(), size_t(len) + 1, format, args);
}
va_end(args);
return message;
}
} // namespace dawn
+35 -27
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -441,7 +442,8 @@ read_file(const string &path, vector<uint8_t> *out, Error *error)
if (!in) {
if (error) {
error->code = Error::Code::Io;
error->message = "failed to open: " + path;
error->message =
format_message(_("failed to open: %s"), path.c_str());
}
return false;
}
@@ -450,7 +452,8 @@ read_file(const string &path, vector<uint8_t> *out, Error *error)
if (sz < 0) {
if (error) {
error->code = Error::Code::Io;
error->message = "failed to size: " + path;
error->message =
format_message(_("failed to size: %s"), path.c_str());
}
return false;
}
@@ -459,7 +462,8 @@ read_file(const string &path, vector<uint8_t> *out, Error *error)
if (sz > 0 && !in.read((char *) out->data(), streamsize(sz))) {
if (error) {
error->code = Error::Code::Io;
error->message = "failed to read: " + path;
error->message =
format_message(_("failed to read: %s"), path.c_str());
}
return false;
}
@@ -1596,12 +1600,16 @@ exif_orientation(span<const uint8_t> exif)
// The order is the default loading order.
constexpr Loader kLoaders[] = {
{"libjpeg-turbo", &detail::load_jpeg, "JPEG", {"image/jpeg"}, {}},
// TRANSLATORS: What a loader reads, as the settings dialog lists it.
// These are format names throughout, bar the odd word such as "raw
// photos" or "(subset)"; leave the names as they are.
{"libjpeg-turbo", &detail::load_jpeg, N_("JPEG"), {"image/jpeg"}, {}},
{"libwebp", &detail::load_webp, "WebP", {"image/webp"}, {}},
{"libwebp", &detail::load_webp, N_("WebP"), {"image/webp"}, {}},
{"Wuffs", &detail::load_wuffs,
"BMP, GIF, JPEG (subset), PNG, PNM, QOI, TARGA, WBMP, WebP (subset)",
N_("BMP, GIF, JPEG (subset), PNG, PNM, QOI, TARGA, WBMP, "
"WebP (subset)"),
{
"image/bmp",
"image/gif",
@@ -1615,17 +1623,17 @@ constexpr Loader kLoaders[] = {
"image/x-tga",
}},
{"ICNS", &detail::load_icns, "ICNS", {"image/x-icns"}, {}},
{"ICNS", &detail::load_icns, N_("ICNS"), {"image/x-icns"}, {}},
{"Photoshop", &detail::load_psd, "PSD/PSB (subset)",
{"Photoshop", &detail::load_psd, N_("PSD/PSB (subset)"),
{"image/vnd.adobe.photoshop"}, {}},
{"OpenRaster", &detail::load_ora, "OpenRaster, Krita",
{"OpenRaster", &detail::load_ora, N_("OpenRaster, Krita"),
{"image/openraster", "application/x-krita"}, {}},
// Try to extract full-size previews from TIFF/EP-compatible raws.
{"TIFF/EP previews", &detail::load_tiff_ep, "raw photos", {"image/x-dcraw"},
{}},
{"TIFF/EP previews", &detail::load_tiff_ep, N_("raw photos"),
{"image/x-dcraw"}, {}},
{"LibRaw",
#if DAWN_WITH_LIBRAW
@@ -1633,9 +1641,9 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"raw photos", {"image/x-dcraw"}, {}},
N_("raw photos"), {"image/x-dcraw"}, {}},
{"resvg", &detail::load_resvg, "SVG", {"image/svg+xml"}, {}},
{"resvg", &detail::load_resvg, N_("SVG"), {"image/svg+xml"}, {}},
{"librsvg",
#if DAWN_WITH_LIBRSVG
@@ -1643,7 +1651,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"SVG", {"image/svg+xml"}, {}},
N_("SVG"), {"image/svg+xml"}, {}},
{"libXcursor",
#if DAWN_WITH_XCURSOR
@@ -1651,7 +1659,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"Xcursor", {"image/x-xcursor"}, {}},
N_("Xcursor"), {"image/x-xcursor"}, {}},
// Before libheif: JPEG XL's container is ISOBMFF too, and we would rather
// not rely on libheif rejecting an unknown ftyp brand.
@@ -1661,7 +1669,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"JPEG XL", {"image/jxl"}, {}},
N_("JPEG XL"), {"image/jxl"}, {}},
{"libheif",
#if DAWN_WITH_LIBHEIF
@@ -1669,7 +1677,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"AVIF, HEIC, HEIF",
N_("AVIF, HEIC, HEIF"),
{
"image/avif",
"image/heic",
@@ -1683,7 +1691,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"JPEG 2000",
N_("JPEG 2000"),
// Not image/jpx or image/jpm: OpenJPEG decodes neither JPX (Part 2)
// nor compound JPM, and claiming them would only fail later.
{"image/jp2", "image/x-jp2-codestream"}, {}},
@@ -1695,7 +1703,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"TIFF", {"image/tiff"}, {}},
N_("TIFF"), {"image/tiff"}, {}},
{"jxrlib",
#if DAWN_WITH_JXRLIB
@@ -1703,7 +1711,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"JPEG XR", {"image/jxr", "image/vnd.ms-photo"}, {}},
N_("JPEG XR"), {"image/jxr", "image/vnd.ms-photo"}, {}},
{"libwmf",
#if DAWN_WITH_LIBWMF
@@ -1711,13 +1719,13 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"WMF", {"image/wmf", "image/x-wmf"}, {}},
N_("WMF"), {"image/wmf", "image/x-wmf"}, {}},
{"Rust",
#if DAWN_WITH_DNRS
&detail::load_dnrs,
"BMP, DDS, farbfeld, GIF, ICO, JPEG, OpenEXR, PNG, PNM, QOI, "
"Radiance HDR, TARGA, TIFF, WebP, XBM, XPM, ...",
N_("BMP, DDS, farbfeld, GIF, ICO, JPEG, OpenEXR, PNG, PNM, QOI, "
"Radiance HDR, TARGA, TIFF, WebP, XBM, XPM, ..."),
{}, &detail::dnrs_media_types},
#else
{}, {}, {}, {}},
@@ -1746,7 +1754,7 @@ constexpr Loader kLoaders[] = {
#else
{},
#endif
"PDF", {}, {}},
N_("PDF"), {}, {}},
};
// A subset of shared-mime-info, chiefly motivated by the suckiness of raw
@@ -1810,7 +1818,7 @@ open_from_data(span<const uint8_t> data, const OpenContext &ctx, Error *error)
{
detail::OpenTimingGuard timing(ctx.timing);
if (data.empty()) {
set_error(error, "empty input");
set_error(error, _("empty input"));
return nullptr;
}
@@ -1837,7 +1845,7 @@ open_from_data(span<const uint8_t> data, const OpenContext &ctx, Error *error)
if (!image) {
if (error && error->message.empty())
set_error(error, "unrecognized or unsupported image format");
set_error(error, _("unrecognized or unsupported image format"));
return nullptr;
}
@@ -1859,7 +1867,7 @@ open(const OpenContext &ctx, Error *error)
{
detail::OpenTimingGuard timing(ctx.timing);
if (ctx.uri.empty()) {
set_error(error, "empty URI");
set_error(error, _("empty URI"));
return nullptr;
}
vector<uint8_t> data;
+11 -10
View File
@@ -11,6 +11,7 @@
// producing no pages at all.
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -86,7 +87,7 @@ CGPDFRenderClosure::render_internal(
{
CGPDFPageRef page = CGPDFDocumentGetPage(document_.get(), page_);
if (!page) {
set_error(error, "no such page");
set_error(error, _("no such page"));
return nullptr;
}
@@ -99,7 +100,7 @@ CGPDFRenderClosure::render_internal(
cgpdf_page_size(page, &pw, &ph);
double w = ceil(pw * zoom), h = ceil(ph * zoom);
if (w < 1 || h < 1 || w > kMaxDimension || h > kMaxDimension) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
return nullptr;
}
@@ -108,7 +109,7 @@ CGPDFRenderClosure::render_internal(
// sRGB, which then genuinely is one, and leave the rest to lcms2.
CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (!srgb) {
set_error(error, "cannot create an sRGB colour space");
set_error(error, _("cannot create an sRGB colour space"));
return nullptr;
}
@@ -123,7 +124,7 @@ CGPDFRenderClosure::render_internal(
if (!context) {
if (icc)
CFRelease(icc);
set_error(error, "cannot create a bitmap context");
set_error(error, _("cannot create a bitmap context"));
return nullptr;
}
@@ -142,7 +143,7 @@ CGPDFRenderClosure::render_internal(
if (!image) {
if (icc)
CFRelease(icc);
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -169,14 +170,14 @@ detail::load_cgpdf(
CFDataRef bytes =
CFDataCreate(kCFAllocatorDefault, data.data(), CFIndex(data.size()));
if (!bytes) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData(bytes);
CFRelease(bytes);
if (!provider) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -184,20 +185,20 @@ detail::load_cgpdf(
CGPDFDocumentCreateWithProvider(provider), CGPDFDocumentRelease);
CGDataProviderRelease(provider);
if (!document) {
set_error(error, "not a PDF document");
set_error(error, _("not a PDF document"));
return nullptr;
}
// Core Graphics opens encrypted documents, then draws nothing of them.
if (CGPDFDocumentIsEncrypted(document.get()) &&
!CGPDFDocumentUnlockWithPassword(document.get(), "")) {
set_error(error, "the document is password-protected");
set_error(error, _("the document is password-protected"));
return nullptr;
}
size_t count = CGPDFDocumentGetNumberOfPages(document.get());
if (!count) {
set_error(error, "the document has no pages");
set_error(error, _("the document has no pages"));
return nullptr;
}
+14 -13
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -36,22 +38,21 @@ struct FrameGuard {
using DecoderPtr = unique_ptr<dnrs_decoder, DecoderDeleter>;
using DnrsErrorPtr = unique_ptr<dnrs_error, ErrorDeleter>;
// "Rust" names the loader, and the codec is what image-rs picked within it;
// neither is a word, so only the punctuation around them is up for grabs.
static string
dnrs_prefix(const char *codec = nullptr)
dnrs_wrap(const char *codec, const char *detail)
{
string message = "Rust";
if (codec && *codec)
message += string(" (") + codec + ")";
return message + ": ";
return format_message(_("Rust (%s): %s"), codec, detail);
return format_message(_("Rust: %s"), detail);
}
static string
dnrs_message(dnrs_error *error, const char *codec = nullptr)
{
string message = dnrs_prefix(codec);
const char *detail = error ? dnrs_error_message(error) : nullptr;
message += detail && *detail ? detail : "decoding error";
return message;
return dnrs_wrap(codec, detail && *detail ? detail : _("decoding error"));
}
static bool
@@ -125,17 +126,17 @@ load_frame(const dnrs_frame &frame, Error *error)
bpp = 8;
break;
default:
set_error(error, "Rust: unsupported pixel format");
set_error(error, _("Rust: unsupported pixel format"));
return nullptr;
}
if (!valid_frame(frame, bpp)) {
set_error(error, "Rust: invalid or truncated frame");
set_error(error, _("Rust: invalid or truncated frame"));
return nullptr;
}
ImagePtr image = image_new(frame.width, frame.height);
if (!image) {
set_error(error, "Rust: image allocation failure");
set_error(error, _("Rust: image allocation failure"));
return nullptr;
}
switch (frame.format) {
@@ -232,7 +233,7 @@ detail::load_dnrs(
if (!page_info.width || !page_info.height ||
page_info.width > kMaxDimension ||
page_info.height > kMaxDimension) {
set_error(error, dnrs_prefix(codec) + "invalid image dimensions");
set_error(error, dnrs_wrap(codec, _("invalid image dimensions")));
return nullptr;
}
@@ -257,7 +258,7 @@ detail::load_dnrs(
break;
}
if (!frames) {
set_error(error, dnrs_prefix(codec) + "page has no frames");
set_error(error, dnrs_wrap(codec, _("page has no frames")));
return nullptr;
}
@@ -280,7 +281,7 @@ detail::load_dnrs(
break;
}
if (!pages)
set_error(error, dnrs_prefix(codec) + "image has no pages");
set_error(error, dnrs_wrap(codec, _("image has no pages")));
return pages;
}
+7 -6
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -32,7 +33,7 @@ load_heif_image(heif_image_handle *handle, const OpenContext &ctx, Error *error)
int has_alpha = heif_image_handle_has_alpha_channel(handle);
int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle);
if (bit_depth < 0) {
set_error(error, "undefined bit depth");
set_error(error, _("undefined bit depth"));
return nullptr;
}
@@ -58,14 +59,14 @@ load_heif_image(heif_image_handle *handle, const OpenContext &ctx, Error *error)
int w = heif_image_get_width(image, heif_channel_interleaved);
int h = heif_image_get_height(image, heif_channel_interleaved);
if (w <= 0 || h <= 0) {
set_error(error, "invalid image dimensions");
set_error(error, _("invalid image dimensions"));
heif_image_release(image);
return nullptr;
}
ImagePtr result = image_new(uint32_t(w), uint32_t(h));
if (!result) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
heif_image_release(image);
return nullptr;
}
@@ -148,7 +149,7 @@ load_heif_image(heif_image_handle *handle, const OpenContext &ctx, Error *error)
// Chiefly PQ and HLG, which have no ICC v2 equivalent; those
// images stay as wrong as they were before, just not more so.
add_warning(
ctx, "unrepresentable nclx colour space, assuming sRGB");
ctx, _("unrepresentable nclx colour space, assuming sRGB"));
}
if (nclx)
heif_nclx_color_profile_free(nclx);
@@ -208,7 +209,7 @@ detail::load_heif(
// The library is generally awful through and through.
heif_context *hctx = heif_context_alloc();
if (!hctx) {
set_error(error, "failed to obtain a libheif context");
set_error(error, _("failed to obtain a libheif context"));
return nullptr;
}
@@ -251,7 +252,7 @@ detail::load_heif(
heif_context_free(hctx);
if (!head) {
set_error(error, "empty or unsupported image");
set_error(error, _("empty or unsupported image"));
return nullptr;
}
+24 -21
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -268,7 +269,7 @@ decode_low_depth(const Entry &entry, const IconInfo &info,
: info.encoding == Encoding::Palette4 ? (pixels + 1) / 2
: pixels;
if (entry.data.size() < bytes * (info.combined_mask ? 2 : 1)) {
set_error(error, "truncated indexed icon data");
set_error(error, _("truncated indexed icon data"));
return nullptr;
}
@@ -283,7 +284,7 @@ decode_low_depth(const Entry &entry, const IconInfo &info,
ImagePtr image = image_new(info.width, info.height);
if (!image) {
set_error(error, "failed to allocate indexed icon image");
set_error(error, _("failed to allocate indexed icon image"));
return nullptr;
}
for (uint32_t y = 0; y < info.height; y++) {
@@ -316,25 +317,25 @@ decode_rle_channel(
size_t in = 0, out = 0;
while (out < output.size()) {
if (in >= data.size()) {
set_error(error, "truncated RLE icon data");
set_error(error, _("truncated RLE icon data"));
return false;
}
uint8_t control = data[in++];
size_t count = control < 128 ? size_t(control) + 1 : control - 125;
if (count > output.size() - out) {
set_error(error, "RLE icon run exceeds its channel");
set_error(error, _("RLE icon run exceeds its channel"));
return false;
}
if (control < 128) {
if (count > data.size() - in) {
set_error(error, "truncated RLE icon literal");
set_error(error, _("truncated RLE icon literal"));
return false;
}
copy_n(data.data() + in, count, output.data() + out);
in += count;
} else {
if (in >= data.size()) {
set_error(error, "truncated RLE icon repeat");
set_error(error, _("truncated RLE icon repeat"));
return false;
}
fill_n(output.data() + out, count, data[in++]);
@@ -349,7 +350,7 @@ static ImagePtr
decode_argb_rle(span<const uint8_t> data, const IconInfo &info, Error *error)
{
if (data.size() < 4 || memcmp(data.data(), "ARGB", 4)) {
set_error(error, "invalid ARGB icon data");
set_error(error, _("invalid ARGB icon data"));
return nullptr;
}
data = data.subspan(4);
@@ -363,7 +364,7 @@ decode_argb_rle(span<const uint8_t> data, const IconInfo &info, Error *error)
ImagePtr image = image_new(info.width, info.height);
if (!image) {
set_error(error, "failed to allocate ARGB icon image");
set_error(error, _("failed to allocate ARGB icon image"));
return nullptr;
}
for (uint32_t y = 0; y < info.height; y++)
@@ -388,7 +389,7 @@ decode_rgb24(const Entry &entry, const IconInfo &info,
if (!interleaved) {
if (entry.type == fourcc('i', 't', '3', '2')) {
if (data.size() < 4) {
set_error(error, "truncated 128x128 icon prefix");
set_error(error, _("truncated 128x128 icon prefix"));
return nullptr;
}
data = data.subspan(4);
@@ -404,7 +405,7 @@ decode_rgb24(const Entry &entry, const IconInfo &info,
span<const uint8_t> mask = mask_data(entries, info, &one_bit_mask);
ImagePtr image = image_new(info.width, info.height);
if (!image) {
set_error(error, "failed to allocate RGB icon image");
set_error(error, _("failed to allocate RGB icon image"));
return nullptr;
}
for (uint32_t y = 0; y < info.height; y++)
@@ -428,12 +429,12 @@ decode_argb_raw(span<const uint8_t> data, const IconInfo &info, Error *error)
{
size_t pixels = size_t(info.width) * info.height;
if (data.size() != pixels * 4) {
set_error(error, "unsupported ARGB icon encoding");
set_error(error, _("unsupported ARGB icon encoding"));
return nullptr;
}
ImagePtr image = image_new(info.width, info.height);
if (!image) {
set_error(error, "failed to allocate ARGB icon image");
set_error(error, _("failed to allocate ARGB icon image"));
return nullptr;
}
for (uint32_t y = 0; y < info.height; y++)
@@ -458,7 +459,7 @@ decode_entry(const Entry &entry, const vector<Entry> &entries,
#if DAWN_WITH_OPENJPEG
return detail::load_openjpeg(entry.data, nested, error);
#else
set_error(error, "JPEG 2000 support is disabled");
set_error(error, _("JPEG 2000 support is disabled"));
return nullptr;
#endif
}
@@ -487,12 +488,12 @@ detail::load_icns(
span<const uint8_t> data, const OpenContext &ctx, Error *error)
{
if (data.size() < 8 || memcmp(data.data(), "icns", 4)) {
set_error(error, "not an ICNS image");
set_error(error, _("not an ICNS image"));
return nullptr;
}
uint32_t declared = be32(data.data() + 4);
if (declared < 8 || declared > data.size()) {
set_error(error, "invalid ICNS file length");
set_error(error, _("invalid ICNS file length"));
return nullptr;
}
@@ -500,14 +501,15 @@ detail::load_icns(
size_t offset = 8;
while (offset < declared) {
if (declared - offset < 8) {
add_warning(ctx, "truncated ICNS entry header");
add_warning(ctx, _("truncated ICNS entry header"));
break;
}
uint32_t type = be32(data.data() + offset);
uint32_t length = be32(data.data() + offset + 4);
if (length < 8 || length > declared - offset) {
add_warning(
ctx, "invalid ICNS " + type_name(type) + " entry length");
add_warning(ctx,
format_message(_("invalid ICNS %s entry length"),
type_name(type).c_str()));
break;
}
entries.push_back({type, data.subspan(offset + 8, size_t(length) - 8)});
@@ -523,12 +525,13 @@ detail::load_icns(
if (ctx.first_frame_only)
break;
} else if (!suberror.message.empty()) {
add_warning(
ctx, "ICNS " + type_name(entry.type) + ": " + suberror.message);
add_warning(ctx,
format_message(_("ICNS %s: %s"), type_name(entry.type).c_str(),
suberror.message.c_str()));
}
}
if (!head) {
set_error(error, "empty or unsupported ICNS image");
set_error(error, _("empty or unsupported ICNS image"));
return nullptr;
}
for (Image *page = head.get(); page; page = page->page_next.get())
+11 -10
View File
@@ -10,6 +10,7 @@
// or GdkPixbuf.
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -135,11 +136,11 @@ imageio_target_space(
// Display P3 would narrow the loss without removing it, and this is
// a fallback loader--revisit if anyone actually views EXR files in anger.
if (source && CGColorSpaceUsesExtendedRange(source))
add_warning(ctx, "extended range colours clipped to sRGB");
add_warning(ctx, _("extended range colours clipped to sRGB"));
CGColorSpaceRef srgb = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (!srgb) {
set_error(error, "cannot create an sRGB colour space");
set_error(error, _("cannot create an sRGB colour space"));
return nullptr;
}
*icc = CGColorSpaceCopyICCData(srgb);
@@ -151,7 +152,7 @@ load_imageio_image(CGImageRef cg, const OpenContext &ctx, Error *error)
{
size_t width = CGImageGetWidth(cg), height = CGImageGetHeight(cg);
if (!width || !height || width > kMaxDimension || height > kMaxDimension) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
return nullptr;
}
@@ -181,7 +182,7 @@ load_imageio_image(CGImageRef cg, const OpenContext &ctx, Error *error)
if (!context) {
if (icc)
CFRelease(icc);
set_error(error, "cannot create a bitmap context");
set_error(error, _("cannot create a bitmap context"));
return nullptr;
}
@@ -189,7 +190,7 @@ load_imageio_image(CGImageRef cg, const OpenContext &ctx, Error *error)
if (!image) {
if (icc)
CFRelease(icc);
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -215,7 +216,7 @@ load_imageio_indexes(CGImageSourceRef source, CFDictionaryRef options,
const OpenContext &ctx, Error *error)
{
if (CGImageSourceGetStatus(source) != kCGImageStatusComplete) {
set_error(error, "incomplete or unsupported ImageIO image");
set_error(error, _("incomplete or unsupported ImageIO image"));
return nullptr;
}
@@ -235,7 +236,7 @@ load_imageio_indexes(CGImageSourceRef source, CFDictionaryRef options,
ImagePtr image;
CGImageRef cg = CGImageSourceCreateImageAtIndex(source, i, options);
if (!cg) {
set_error(&suberror, "ImageIO decoding error");
set_error(&suberror, _("ImageIO decoding error"));
} else {
image = load_imageio_image(cg, ctx, &suberror);
CGImageRelease(cg);
@@ -270,7 +271,7 @@ load_imageio_indexes(CGImageSourceRef source, CFDictionaryRef options,
break;
}
if (!head) {
set_error(error, "empty ImageIO image");
set_error(error, _("empty ImageIO image"));
return nullptr;
}
@@ -289,7 +290,7 @@ detail::load_imageio(
CFDataRef wrapper = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
data.data(), CFIndex(data.size()), kCFAllocatorNull);
if (!wrapper) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -307,7 +308,7 @@ detail::load_imageio(
image = load_imageio_indexes(source, options, ctx, error);
CFRelease(source);
} else {
set_error(error, "not an ImageIO-decodable image");
set_error(error, _("not an ImageIO-decodable image"));
}
CFRelease(options);
+4 -2
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -537,7 +538,8 @@ load_jpeg_finalize(ImagePtr &image, bool cmyk, bool argb, int bits,
append_page(image, tail, std::move(sub));
else
add_warning(ctx,
"MPF image " + to_string(i + 2) + ": " + suberror.message);
format_message(_("MPF image %d: %s"), int(i + 2),
suberror.message.c_str()));
}
}
@@ -646,7 +648,7 @@ load_libjpeg_turbo(span<const uint8_t> data, const OpenContext &ctx,
ImagePtr image = image_new(uint32_t(width), uint32_t(height));
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
+16 -15
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -70,14 +71,14 @@ setup_decoder(JxlLoadContext &ctx, span<const uint8_t> data, Error *error)
if (JxlDecoderSubscribeEvents(ctx.dec,
JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME |
JXL_DEC_FULL_IMAGE | JXL_DEC_BOX) != JXL_DEC_SUCCESS) {
set_error(error, "failed to subscribe to libjxl events");
set_error(error, _("failed to subscribe to libjxl events"));
return false;
}
// dawn rotates at display time, off Image::orientation, so letting libjxl
// bake the orientation into the pixels here would apply it twice.
if (JxlDecoderSetKeepOrientation(ctx.dec, JXL_TRUE) != JXL_DEC_SUCCESS) {
set_error(error, "failed to retain the JPEG XL orientation");
set_error(error, _("failed to retain the JPEG XL orientation"));
return false;
}
@@ -86,7 +87,7 @@ setup_decoder(JxlLoadContext &ctx, span<const uint8_t> data, Error *error)
if (JxlDecoderSetInput(ctx.dec, data.data(), data.size()) !=
JXL_DEC_SUCCESS) {
set_error(error, "failed to hand the data to libjxl");
set_error(error, _("failed to hand the data to libjxl"));
return false;
}
@@ -174,7 +175,7 @@ take_icc_profile(JxlLoadContext &ctx)
if (JxlDecoderGetColorAsICCProfile(ctx.dec, JXL_COLOR_PROFILE_TARGET_DATA,
ctx.icc.data(), size) != JXL_DEC_SUCCESS) {
ctx.icc.clear();
add_warning(*ctx.octx, "failed to read the ICC profile");
add_warning(*ctx.octx, _("failed to read the ICC profile"));
}
}
@@ -185,7 +186,7 @@ take_frame_header(JxlLoadContext &ctx, Error *error)
{
JxlFrameHeader frame = {};
if (JxlDecoderGetFrameHeader(ctx.dec, &frame) != JXL_DEC_SUCCESS) {
set_error(error, "failed to read a frame header");
set_error(error, _("failed to read a frame header"));
return false;
}
@@ -204,14 +205,14 @@ bind_frame_buffer(JxlLoadContext &ctx, Error *error)
size_t size = 0;
if (JxlDecoderImageOutBufferSize(ctx.dec, &kFormat, &size) !=
JXL_DEC_SUCCESS) {
set_error(error, "failed to size the output buffer");
set_error(error, _("failed to size the output buffer"));
return false;
}
ctx.scratch.resize(size);
if (JxlDecoderSetImageOutBuffer(ctx.dec, &kFormat, ctx.scratch.data(),
ctx.scratch.size()) != JXL_DEC_SUCCESS) {
set_error(error, "failed to set the output buffer");
set_error(error, _("failed to set the output buffer"));
return false;
}
return true;
@@ -222,13 +223,13 @@ append_decoded_frame(JxlLoadContext &ctx, Error *error)
{
// This also catches a frame arriving before the header we subscribed to.
if (!ctx.info.xsize || !ctx.info.ysize) {
set_error(error, "invalid image dimensions");
set_error(error, _("invalid image dimensions"));
return false;
}
ImagePtr image = image_new(ctx.info.xsize, ctx.info.ysize);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return false;
}
@@ -256,14 +257,14 @@ process_event(JxlLoadContext &ctx, bool *done, Error *error)
{
switch (JxlDecoderProcessInput(ctx.dec)) {
case JXL_DEC_ERROR:
set_error(error, "invalid or unsupported JPEG XL data");
set_error(error, _("invalid or unsupported JPEG XL data"));
return false;
case JXL_DEC_NEED_MORE_INPUT:
set_error(error, "truncated JPEG XL data");
set_error(error, _("truncated JPEG XL data"));
return false;
case JXL_DEC_BASIC_INFO:
if (JxlDecoderGetBasicInfo(ctx.dec, &ctx.info) != JXL_DEC_SUCCESS) {
set_error(error, "failed to read the JPEG XL header");
set_error(error, _("failed to read the JPEG XL header"));
return false;
}
break;
@@ -291,7 +292,7 @@ process_event(JxlLoadContext &ctx, bool *done, Error *error)
*done = true;
break;
default:
set_error(error, "unexpected libjxl decoder state");
set_error(error, _("unexpected libjxl decoder state"));
return false;
}
return true;
@@ -307,7 +308,7 @@ detail::load_jxl(
ctx.octx = &octx;
ctx.dec = JxlDecoderCreate(nullptr);
if (!ctx.dec) {
set_error(error, "failed to obtain a libjxl decoder");
set_error(error, _("failed to obtain a libjxl decoder"));
return nullptr;
}
if (!setup_decoder(ctx, data, error))
@@ -319,7 +320,7 @@ detail::load_jxl(
finish_box(ctx);
if (!ctx.result) {
set_error(error, "empty or unsupported image");
set_error(error, _("empty or unsupported image"));
return nullptr;
}
+10 -9
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -212,18 +213,18 @@ decode_image(JxrLoadContext &ctx, const OpenContext &octx, Error *error)
I32 width = 0, height = 0;
if (ctx.decoder->GetPixelFormat(ctx.decoder, &format) ||
ctx.decoder->GetSize(ctx.decoder, &width, &height)) {
set_error(error, "cannot read the JPEG XR image header");
set_error(error, _("cannot read the JPEG XR image header"));
return nullptr;
}
if (width <= 0 || height <= 0 || uint32_t(width) > kMaxDimension ||
uint32_t(height) > kMaxDimension) {
set_error(error, "invalid image dimensions");
set_error(error, _("invalid image dimensions"));
return nullptr;
}
const Layout *layout = find_layout(format);
if (!layout && !(layout = open_converter(ctx))) {
set_error(error, "unsupported JPEG XR pixel format");
set_error(error, _("unsupported JPEG XR pixel format"));
return nullptr;
}
@@ -242,13 +243,13 @@ decode_image(JxrLoadContext &ctx, const OpenContext &octx, Error *error)
? ctx.converter->Copy(ctx.converter, &rect, buffer.data(), U32(stride))
: ctx.decoder->Copy(ctx.decoder, &rect, buffer.data(), U32(stride));
if (err) {
set_error(error, "cannot decode the JPEG XR image");
set_error(error, _("cannot decode the JPEG XR image"));
return nullptr;
}
ImagePtr image = image_new(uint32_t(width), uint32_t(height));
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -276,27 +277,27 @@ detail::load_jxr(
static const uint8_t signature[] = {'I', 'I', 0xBC};
if (data.size() < sizeof signature ||
memcmp(data.data(), signature, sizeof signature)) {
set_error(error, "not a JPEG XR image");
set_error(error, _("not a JPEG XR image"));
return nullptr;
}
JxrLoadContext ctx;
if (PKCreateFactory(&ctx.factory, PK_SDK_VERSION) ||
PKCreateCodecFactory(&ctx.codecs, WMP_SDK_VERSION)) {
set_error(error, "failed to obtain a jxrlib decoder");
set_error(error, _("failed to obtain a jxrlib decoder"));
return nullptr;
}
// jxrlib only ever reads from a stream it decodes, hence the cast.
if (ctx.factory->CreateStreamFromMemory(
&ctx.stream, (void *) data.data(), data.size())) {
set_error(error, "failed to obtain a jxrlib stream");
set_error(error, _("failed to obtain a jxrlib stream"));
return nullptr;
}
if (ctx.codecs->CreateCodec(
&IID_PKImageWmpDecode, (void **) &ctx.decoder) ||
ctx.decoder->Initialize(ctx.decoder, ctx.stream)) {
set_error(error, "unsupported or unrecognized JPEG XR image");
set_error(error, _("unsupported or unrecognized JPEG XR image"));
return nullptr;
}
+4 -3
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -46,14 +47,14 @@ load_libraw_page(libraw_data_t *iprc, const OpenContext &ctx, Error *error)
// This should have been transformed, and kept, respectively.
if (image->colors != 3 || image->bits != 16) {
set_error(error, "unexpected number of colours, or bit depth");
set_error(error, _("unexpected number of colours, or bit depth"));
libraw_dcraw_clear_mem(image);
return nullptr;
}
ImagePtr result = image_new(image->width, image->height);
if (!result) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
libraw_dcraw_clear_mem(image);
return nullptr;
}
@@ -77,7 +78,7 @@ detail::load_libraw(
// callback flag (unlike in older LibRaw releases fiv-io.c targeted).
libraw_data_t *iprc = libraw_init(LIBRAW_OPTIONS_NO_DATAERR_CALLBACK);
if (!iprc) {
set_error(error, "failed to obtain a LibRaw handle");
set_error(error, _("failed to obtain a LibRaw handle"));
return nullptr;
}
+7 -5
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -80,7 +82,7 @@ LibrsvgRenderClosure::render_internal(
.x = 0, .y = 0, .width = width_ * scale, .height = height_ * scale};
double w = ceil(viewport.width), h = ceil(viewport.height);
if (w < 1 || h < 1 || w > kMaxRenderDimension || h > kMaxRenderDimension) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
return nullptr;
}
@@ -88,7 +90,7 @@ LibrsvgRenderClosure::render_internal(
cairo_surface_t *surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, int(uw), int(uh));
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
cairo_surface_destroy(surface);
return nullptr;
}
@@ -114,7 +116,7 @@ LibrsvgRenderClosure::render_internal(
cairo_surface_flush(surface);
ImagePtr image = image_new(uw, uh);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
cairo_surface_destroy(surface);
return nullptr;
}
@@ -161,7 +163,7 @@ detail::load_librsvg(
rsvg_handle_get_intrinsic_dimensions(handle, nullptr, nullptr, nullptr,
nullptr, &has_viewport, &viewbox);
if (!has_viewport) {
set_error(error, "cannot compute pixel dimensions");
set_error(error, _("cannot compute pixel dimensions"));
g_object_unref(handle);
return nullptr;
}
@@ -169,7 +171,7 @@ detail::load_librsvg(
h = viewbox.height;
}
if (!(w > 0) || !(h > 0)) {
set_error(error, "cannot compute pixel dimensions");
set_error(error, _("cannot compute pixel dimensions"));
g_object_unref(handle);
return nullptr;
}
+15 -10
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -86,7 +88,7 @@ wmf_open_and_scan(WmfApi &wmf, vector<uint8_t> &data, wmfD_Rect *bbox,
uint32_t *width, uint32_t *height, Error *error)
{
if (data.size() > size_t(LONG_MAX)) {
set_error(error, "libwmf: input is too large");
set_error(error, _("libwmf: input is too large"));
return false;
}
@@ -96,7 +98,8 @@ wmf_open_and_scan(WmfApi &wmf, vector<uint8_t> &data, wmfD_Rect *bbox,
WMF_OPT_NO_ERROR | WMF_OPT_NO_DEBUG;
wmf_error_t status = wmf_api_create(&wmf.api, flags, &options);
if (status != wmf_E_None) {
set_error(error, string("libwmf: ") + wmf_error_string(status));
set_error(
error, format_message(_("libwmf: %s"), wmf_error_string(status)));
return false;
}
@@ -104,7 +107,7 @@ wmf_open_and_scan(WmfApi &wmf, vector<uint8_t> &data, wmfD_Rect *bbox,
// way to destroy; only its encoders dispose of one themselves.
wmf_gd_t *device = WMF_GD_GetData(wmf.api);
if (!(device->flags & WMF_GD_SUPPORTS_PNG)) {
set_error(error, "libwmf: built without PNG support");
set_error(error, _("libwmf: built without PNG support"));
return false;
}
device->type = wmf_gd_png;
@@ -112,7 +115,8 @@ wmf_open_and_scan(WmfApi &wmf, vector<uint8_t> &data, wmfD_Rect *bbox,
status = wmf_mem_open(wmf.api, data.data(), long(data.size()));
if (status != wmf_E_None) {
set_error(error, string("libwmf: ") + wmf_error_string(status));
set_error(
error, format_message(_("libwmf: %s"), wmf_error_string(status)));
return false;
}
@@ -122,11 +126,12 @@ wmf_open_and_scan(WmfApi &wmf, vector<uint8_t> &data, wmfD_Rect *bbox,
if (status == wmf_E_None)
status = wmf_display_size(wmf.api, &w, &h, 72, 72);
if (status != wmf_E_None) {
set_error(error, string("libwmf: ") + wmf_error_string(status));
set_error(
error, format_message(_("libwmf: %s"), wmf_error_string(status)));
return false;
}
if (!w || !h || w > kMaxDimension || h > kMaxDimension) {
set_error(error, "libwmf: invalid image dimensions");
set_error(error, _("libwmf: invalid image dimensions"));
return false;
}
@@ -180,9 +185,9 @@ render_wmf(vector<uint8_t> &data, uint32_t *width, uint32_t *height,
status == wmf_E_None && device->memory ? png_length(device->memory) : 0;
if (!length) {
set_error(error,
string("libwmf: ") +
(status == wmf_E_None ? "GD device produced no image"
: wmf_error_string(status)));
format_message(_("libwmf: %s"),
status == wmf_E_None ? _("GD device produced no image")
: wmf_error_string(status)));
return nullptr;
}
return detail::load_wuffs(
@@ -202,7 +207,7 @@ WmfRenderClosure::render_internal(
{
double w = ceil(width_ * scale), h = ceil(height_ * scale);
if (w < 1 || h < 1 || w > kMaxDimension || h > kMaxDimension) {
set_error(error, "libwmf: image dimensions overflow");
set_error(error, _("libwmf: image dimensions overflow"));
return nullptr;
}
OpenContext ctx;
+10 -9
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -158,7 +159,7 @@ static bool
open_codec(OpenJpegLoadContext &ctx, OPJ_CODEC_FORMAT format, Error *error)
{
if (!(ctx.codec = opj_create_decompress(format))) {
set_error(error, "failed to obtain an OpenJPEG decoder");
set_error(error, _("failed to obtain an OpenJPEG decoder"));
return false;
}
@@ -168,7 +169,7 @@ open_codec(OpenJpegLoadContext &ctx, OPJ_CODEC_FORMAT format, Error *error)
opj_dparameters_t parameters = {};
opj_set_default_decoder_parameters(&parameters);
if (!opj_setup_decoder(ctx.codec, &parameters)) {
set_error(error, "failed to set up the OpenJPEG decoder");
set_error(error, _("failed to set up the OpenJPEG decoder"));
return false;
}
@@ -176,7 +177,7 @@ open_codec(OpenJpegLoadContext &ctx, OPJ_CODEC_FORMAT format, Error *error)
opj_decoder_set_strict_mode(ctx.codec, OPJ_FALSE);
if (!(ctx.stream = opj_stream_default_create(OPJ_TRUE))) {
set_error(error, "failed to obtain an OpenJPEG stream");
set_error(error, _("failed to obtain an OpenJPEG stream"));
return false;
}
@@ -282,7 +283,7 @@ plan_layout(const opj_image_t &image, Layout *out, Error *error)
OPJ_COLOR_SPACE space = image.color_space;
uint32_t n = image.numcomps;
if (!n) {
set_error(error, "no image components");
set_error(error, _("no image components"));
return false;
}
@@ -306,11 +307,11 @@ plan_layout(const opj_image_t &image, Layout *out, Error *error)
break;
default:
// CMYK would need an ink profile we have no way to guess at.
set_error(error, "unsupported JPEG 2000 colour space");
set_error(error, _("unsupported JPEG 2000 colour space"));
return false;
}
if (n < colours) {
set_error(error, "too few image components");
set_error(error, _("too few image components"));
return false;
}
@@ -378,7 +379,7 @@ build_image(OpenJpegLoadContext &ctx, Error *error)
const opj_image_comp_t &c = image.comps[wanted[i]];
if (!c.data || !c.w || !c.h || !c.prec || c.prec > 32) {
set_error(error, "unsupported image component");
set_error(error, _("unsupported image component"));
return nullptr;
}
samplers[i] =
@@ -387,7 +388,7 @@ build_image(OpenJpegLoadContext &ctx, Error *error)
ImagePtr out = image_new(first.w, first.h);
if (!out) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -409,7 +410,7 @@ detail::load_openjpeg(
{
OPJ_CODEC_FORMAT format = detect_codec(data);
if (format == OPJ_CODEC_UNKNOWN) {
set_error(error, "not a JPEG 2000 image");
set_error(error, _("not a JPEG 2000 image"));
return nullptr;
}
+8 -5
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -259,13 +261,13 @@ ImagePtr
detail::load_ora(span<const uint8_t> data, const OpenContext &ctx, Error *error)
{
if (data.size() < 4 || le32(data.data()) != kSignatureLocal) {
set_error(error, "not a ZIP archive");
set_error(error, _("not a ZIP archive"));
return nullptr;
}
vector<ZipEntry> entries;
if (!read_directory(data, &entries)) {
set_error(error, "malformed ZIP archive");
set_error(error, _("malformed ZIP archive"));
return nullptr;
}
@@ -278,7 +280,7 @@ detail::load_ora(span<const uint8_t> data, const OpenContext &ctx, Error *error)
if (mimetype && read_entry(data, *mimetype, &type))
id = string_view((const char *) type.data(), type.size());
if (id != "image/openraster" && id != "application/x-krita") {
set_error(error, "not an OpenRaster or Krita image");
set_error(error, _("not an OpenRaster or Krita image"));
return nullptr;
}
@@ -300,11 +302,12 @@ detail::load_ora(span<const uint8_t> data, const OpenContext &ctx, Error *error)
}
}
if (!found) {
set_error(error, "the archive carries no composite image");
set_error(error, _("the archive carries no composite image"));
return nullptr;
}
if (found != kComposites[0])
add_warning(ctx, string(found) + " is a reduced-size preview");
add_warning(
ctx, format_message(_("%s is a reduced-size preview"), found));
return detail::load_wuffs(png, ctx, error);
}
+11 -11
View File
@@ -8,6 +8,7 @@
// Deliberately not advertising support elsewhere, as with Core Graphics PDF.
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -76,7 +77,7 @@ PopplerRenderClosure::render_internal(
// A PDF unit is 1/72 inch, see load-cgpdf.mm.
double dpi = dpi_ * scale;
if (!isfinite(dpi) || dpi <= 0) {
set_error(error, "invalid scale");
set_error(error, _("invalid scale"));
return nullptr;
}
@@ -84,10 +85,9 @@ PopplerRenderClosure::render_internal(
double w = 0, h = 0;
{
lock_guard<mutex> guard(document_->lock);
unique_ptr<poppler::page> page(
document_->document->create_page(page_));
unique_ptr<poppler::page> page(document_->document->create_page(page_));
if (!page) {
set_error(error, "no such page");
set_error(error, _("no such page"));
return nullptr;
}
@@ -104,7 +104,7 @@ PopplerRenderClosure::render_internal(
double cw = ceil(w), ch = ceil(h);
if (!(w > 0 && h > 0) || cw > kMaxDimension || ch > kMaxDimension ||
cw * kBytesPerPixel * ch > UINT32_MAX) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
return nullptr;
}
@@ -124,13 +124,13 @@ PopplerRenderClosure::render_internal(
if (!raster.is_valid() || raster.format() != poppler::image::format_rgb24 ||
rw < 1 || rh < 1 || abs(rw - w) > 1 || abs(rh - h) > 1 ||
raster.bytes_per_row() < rw * 3) {
set_error(error, "Poppler rendering failed");
set_error(error, _("Poppler rendering failed"));
return nullptr;
}
ImagePtr image = image_new(uint32_t(rw), uint32_t(rh));
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -150,7 +150,7 @@ detail::load_poppler(
span<const uint8_t> data, const OpenContext &ctx, Error *error)
{
if (!poppler::page_renderer::can_render()) {
set_error(error, "Poppler has been built without Splash");
set_error(error, _("Poppler has been built without Splash"));
return nullptr;
}
@@ -160,17 +160,17 @@ detail::load_poppler(
auto document = make_shared<PopplerDocument>();
document->document.reset(poppler::document::load_from_data(&bytes));
if (!document->document) {
set_error(error, "not a PDF document");
set_error(error, _("not a PDF document"));
return nullptr;
}
if (document->document->is_locked()) {
set_error(error, "the document is password-protected");
set_error(error, _("the document is password-protected"));
return nullptr;
}
int count = document->document->pages();
if (count <= 0) {
set_error(error, "the document has no pages");
set_error(error, _("the document has no pages"));
return nullptr;
}
+23 -19
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -191,7 +193,7 @@ parse_resources(
}
}
if (!r.ok)
add_warning(ctx, "truncated PSD image resources");
add_warning(ctx, _("truncated PSD image resources"));
}
// --- Composite ---------------------------------------------------------------
@@ -237,7 +239,7 @@ read_composite(
{
uint16_t compression = read16(r);
if (!r.ok) {
set_error(error, "truncated PSD image data section");
set_error(error, _("truncated PSD image data section"));
return false;
}
@@ -245,7 +247,7 @@ read_composite(
// image_new() has already bounded the pixel count; this guards the
// remaining multiplications on 32-bit size_t.
if (uint64_t(h.width) * h.height * sample * h.channels > SIZE_MAX) {
set_error(error, "PSD composite too large");
set_error(error, _("PSD composite too large"));
return false;
}
@@ -254,7 +256,7 @@ read_composite(
try {
planes->resize(row * rows);
} catch (const bad_alloc &) {
set_error(error, "PSD composite too large");
set_error(error, _("PSD composite too large"));
return false;
}
@@ -263,7 +265,7 @@ read_composite(
for (size_t i = 0; i < rows; i++) {
const uint8_t *p = take(r, row);
if (!p) {
set_error(error, "truncated PSD composite");
set_error(error, _("truncated PSD composite"));
return false;
}
memcpy(planes->data() + row * i, p, row);
@@ -276,13 +278,13 @@ read_composite(
for (size_t i = 0; i < rows; i++) {
if (unpack_bits(r, planes->data() + row * i, row))
continue;
set_error(error, "truncated PSD composite");
set_error(error, _("truncated PSD composite"));
return false;
}
return true;
default:
// Both ZIP variants only ever show up on layers in practice.
set_error(error, "unsupported PSD composite compression");
set_error(error, _("unsupported PSD composite compression"));
return false;
}
}
@@ -355,7 +357,7 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
Reader r{data};
const uint8_t *signature = take(r, 4);
if (!signature || memcmp(signature, "8BPS", 4)) {
set_error(error, "not a PSD image");
set_error(error, _("not a PSD image"));
return nullptr;
}
@@ -368,13 +370,14 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
h.depth = read16(r);
h.color_mode = read16(r);
if (!r.ok) {
set_error(error, "truncated PSD header");
set_error(error, _("truncated PSD header"));
return nullptr;
}
// Version 2 is PSB, which only widens two of the section lengths.
if (version != 1 && version != 2) {
set_error(error, "unsupported PSD version " + to_string(version));
set_error(
error, format_message(_("unsupported PSD version %u"), version));
return nullptr;
}
h.psb = version == 2;
@@ -391,16 +394,17 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
break;
default:
set_error(error,
string("unsupported PSD colour mode: ") +
color_mode_name(h.color_mode));
format_message(_("unsupported PSD colour mode: %s"),
color_mode_name(h.color_mode)));
return nullptr;
}
if (h.depth != 8 && h.depth != 16) {
set_error(error, "unsupported PSD bit depth " + to_string(h.depth));
set_error(
error, format_message(_("unsupported PSD bit depth %u"), h.depth));
return nullptr;
}
if (h.file_channels < h.color_channels) {
set_error(error, "PSD channel count does not match its colour mode");
set_error(error, _("PSD channel count does not match its colour mode"));
return nullptr;
}
@@ -410,7 +414,7 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
uint32_t resources_length = read32(r);
const uint8_t *resources = take(r, resources_length);
if (!resources) {
set_error(error, "truncated PSD image resources");
set_error(error, _("truncated PSD image resources"));
return nullptr;
}
@@ -421,7 +425,7 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
// count, which is the only place the composite's alpha is announced.
uint64_t layers = h.psb ? read64(r) : read32(r);
if (!r.ok || layers > data.size() - r.offset) {
set_error(error, "truncated PSD layer and mask section");
set_error(error, _("truncated PSD layer and mask section"));
return nullptr;
}
@@ -435,14 +439,14 @@ detail::load_psd(span<const uint8_t> data, const OpenContext &ctx, Error *error)
if (res.dummy_merged_data || data.size() - r.offset < 2) {
set_error(error,
"PSD carries no composite image, "
"it needs to be saved with Maximize Compatibility");
_("PSD carries no composite image, "
"it needs to be saved with Maximize Compatibility"));
return nullptr;
}
ImagePtr image = image_new(h.width, h.height);
if (!image) {
set_error(error, "unsupported PSD image dimensions");
set_error(error, _("unsupported PSD image dimensions"));
return nullptr;
}
+4 -2
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -88,7 +90,7 @@ ResvgRenderClosure::render_internal(
{
double w = ceil(width_ * scale), h = ceil(height_ * scale);
if (w < 1 || h < 1 || w > kMaxRenderDimension || h > kMaxRenderDimension) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
return nullptr;
}
@@ -102,7 +104,7 @@ ResvgRenderClosure::render_internal(
ImagePtr image = image_new(uw, uh);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
+9 -7
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -215,13 +217,13 @@ load_tiff_ep_page(const tiffer *T, const OpenContext &ctx, Error *error)
entry.type == TIFFER_BYTE && entry.remaining_count == 4 &&
entry.p[0] == 1 && entry.p[1] <= 6 && !entry.p[2] && !entry.p[3];
if (!is_tiffep && !is_supported_dng) {
set_error(error, "not a supported TIFF/EP or DNG image");
set_error(error, _("not a supported TIFF/EP or DNG image"));
return nullptr;
}
tiffer fullT = {};
if (!tiff_ep_find_main(T, &fullT)) {
set_error(error, "could not find a main image");
set_error(error, _("could not find a main image"));
return nullptr;
}
@@ -229,13 +231,13 @@ load_tiff_ep_page(const tiffer *T, const OpenContext &ctx, Error *error)
if (!tiffer_find_integer(&fullT, TIFF_ImageWidth, &width) ||
!tiffer_find_integer(&fullT, TIFF_ImageLength, &height) || width <= 0 ||
height <= 0) {
set_error(error, "missing or invalid main image dimensions");
set_error(error, _("missing or invalid main image dimensions"));
return nullptr;
}
TiffEpJpeg out;
if (!tiff_ep_find_jpeg(T, &out)) {
set_error(error, "error looking for a full-size JPEG preview");
set_error(error, _("error looking for a full-size JPEG preview"));
return nullptr;
}
@@ -244,7 +246,7 @@ load_tiff_ep_page(const tiffer *T, const OpenContext &ctx, Error *error)
// Be a bit more generous than that with our crop tolerance.
// TODO(p): Also take into account DNG DefaultCropSize, if present.
if (double(out.pixels) / (double(width) * double(height)) < 0.95) {
set_error(error, "could not find a large enough JPEG preview");
set_error(error, _("could not find a large enough JPEG preview"));
return nullptr;
}
@@ -272,7 +274,7 @@ detail::load_tiff_ep(
{
tiffer T = {};
if (!tiffer_init(&T, data.data(), data.size())) {
set_error(error, "not a TIFF file");
set_error(error, _("not a TIFF file"));
return nullptr;
}
@@ -294,7 +296,7 @@ detail::load_tiff_ep(
}
if (!head)
set_error(error, "not a TIFF/EP or DNG image with a usable preview");
set_error(error, _("not a TIFF/EP or DNG image with a usable preview"));
return head;
}
+12 -13
View File
@@ -13,6 +13,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -109,8 +110,7 @@ tiff_size(thandle_t h)
return ((TiffIo *) h)->len;
}
DAWN_FORMAT(3, 0)
static void
DAWN_FORMAT(3, 0) static void
tiff_error(thandle_t h, const char *module, const char *format, va_list ap)
{
auto *io = (TiffIo *) h;
@@ -124,8 +124,7 @@ tiff_error(thandle_t h, const char *module, const char *format, va_list ap)
add_warning(*io->ctx, string(module) + ": " + buf);
}
DAWN_FORMAT(3, 0)
static void
DAWN_FORMAT(3, 0) static void
tiff_warning(thandle_t h, const char *module, const char *format, va_list ap)
{
auto *io = (TiffIo *) h;
@@ -200,19 +199,19 @@ load_tiff_directory_u16(TIFF *tiff, const OpenContext &ctx, Error *error)
return nullptr;
if (width > kMaxDimension || height > kMaxDimension) {
set_error(error, "image dimensions too large");
set_error(error, _("image dimensions too large"));
return nullptr;
}
tmsize_t scan_bytes = TIFFScanlineSize(tiff);
if (scan_bytes <= 0) {
set_error(error, "invalid TIFF scanline size");
set_error(error, _("invalid TIFF scanline size"));
return nullptr;
}
ImagePtr image = image_new(width, height);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -222,7 +221,7 @@ load_tiff_directory_u16(TIFF *tiff, const OpenContext &ctx, Error *error)
if (grey) {
for (uint32_t y = 0; y < height; y++) {
if (TIFFReadScanline(tiff, scan.data(), y, 0) < 0) {
set_error(error, "TIFF decoding error");
set_error(error, _("TIFF decoding error"));
return nullptr;
}
auto *s = assume_aligned<const uint16_t>(scan.data());
@@ -237,7 +236,7 @@ load_tiff_directory_u16(TIFF *tiff, const OpenContext &ctx, Error *error)
vector<uint16_t> packed(size_t(width) * spp * height);
for (uint32_t y = 0; y < height; y++) {
if (TIFFReadScanline(tiff, scan.data(), y, 0) < 0) {
set_error(error, "TIFF decoding error");
set_error(error, _("TIFF decoding error"));
return nullptr;
}
memcpy(packed.data() + size_t(y) * width * spp, scan.data(),
@@ -308,14 +307,14 @@ load_tiff_directory(TIFF *tiff, const OpenContext &ctx, Error *error)
}
if (img.width > kMaxDimension || img.height > kMaxDimension) {
set_error(error, "image dimensions too large");
set_error(error, _("image dimensions too large"));
TIFFRGBAImageEnd(&img);
return nullptr;
}
ImagePtr image = image_new(img.width, img.height);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
TIFFRGBAImageEnd(&img);
return nullptr;
}
@@ -327,7 +326,7 @@ load_tiff_directory(TIFF *tiff, const OpenContext &ctx, Error *error)
bool ok = TIFFRGBAImageGet(&img, raster.data(), img.width, img.height);
TIFFRGBAImageEnd(&img);
if (!ok) {
set_error(error, "TIFF decoding error");
set_error(error, _("TIFF decoding error"));
return nullptr;
}
@@ -409,7 +408,7 @@ detail::load_tiff(
head.reset();
set_error(error, io.error);
} else if (!head) {
set_error(error, "empty or unsupported TIFF image");
set_error(error, _("empty or unsupported TIFF image"));
}
return head;
}
+17 -13
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -60,7 +62,7 @@ load_webp_still(WebPDecoderConfig *config, const WebPData &wd, bool premultiply,
auto height = uint32_t(config->input.height);
ImagePtr image = image_new(width, height);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -78,7 +80,7 @@ load_webp_still(WebPDecoderConfig *config, const WebPData &wd, bool premultiply,
WebPIDecoder *idec = WebPIDecode(nullptr, 0, config);
if (!idec) {
set_error(error, "WebP decoding error");
set_error(error, _("WebP decoding error"));
return nullptr;
}
@@ -87,13 +89,14 @@ load_webp_still(WebPDecoderConfig *config, const WebPData &wd, bool premultiply,
if (err != VP8_STATUS_OK) {
if (err != VP8_STATUS_SUSPENDED) {
set_error(error,
string("WebP decoding error: ") + webp_status_string(err));
format_message(
_("WebP decoding error: %s"), webp_status_string(err)));
return nullptr;
}
// The undecoded remainder of the buffer is zero, i.e. transparent
// black, which is a reasonable substitute for the missing data.
add_warning(ctx, "image file is truncated");
add_warning(ctx, _("image file is truncated"));
}
widen_bgra8_to_bgra16(*image, buffer.data(), stride);
@@ -108,13 +111,13 @@ load_webp_frame(WebPAnimDecoder *dec, const WebPAnimInfo &info,
uint8_t *buf = nullptr;
int timestamp = 0;
if (!WebPAnimDecoderGetNext(dec, &buf, &timestamp)) {
set_error(error, "WebP decoding error");
set_error(error, _("WebP decoding error"));
return nullptr;
}
ImagePtr image = image_new(info.canvas_width, info.canvas_height);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return nullptr;
}
@@ -139,7 +142,7 @@ load_webp_animated(
WebPAnimDecoder *dec = WebPAnimDecoderNew(&wd, &options);
if (!dec) {
set_error(error, "WebP decoding error");
set_error(error, _("WebP decoding error"));
return nullptr;
}
@@ -147,7 +150,7 @@ load_webp_animated(
WebPAnimDecoderGetInfo(dec, &info);
if (info.canvas_width > kMaxDimension ||
info.canvas_height > kMaxDimension) {
set_error(error, "image dimensions overflow");
set_error(error, _("image dimensions overflow"));
WebPAnimDecoderDelete(dec);
return nullptr;
}
@@ -168,7 +171,7 @@ load_webp_animated(
WebPAnimDecoderDelete(dec);
if (!head)
set_error(error, "the animation has no frames");
set_error(error, _("the animation has no frames"));
return head;
}
@@ -180,7 +183,7 @@ load_webp_metadata(Image &image, const WebPData &wd, const OpenContext &ctx)
WebPDemuxState state = WEBP_DEMUX_PARSE_ERROR;
WebPDemuxer *demux = WebPDemuxPartial(&wd, &state);
if (!demux) {
add_warning(ctx, "demux failure while reading metadata");
add_warning(ctx, _("demux failure while reading metadata"));
return;
}
@@ -226,7 +229,7 @@ detail::load_webp(
// It is wholly zero-initialized by libwebp.
WebPDecoderConfig config = {};
if (!WebPInitDecoderConfig(&config)) {
set_error(error, "libwebp version mismatch");
set_error(error, _("libwebp version mismatch"));
return nullptr;
}
@@ -234,8 +237,9 @@ detail::load_webp(
WebPData wd{data.data(), data.size()};
VP8StatusCode err = WebPGetFeatures(wd.bytes, wd.size, &config.input);
if (err != VP8_STATUS_OK) {
set_error(
error, string("WebP decoding error: ") + webp_status_string(err));
set_error(error,
format_message(
_("WebP decoding error: %s"), webp_status_string(err)));
return nullptr;
}
+16 -15
View File
@@ -27,6 +27,7 @@
#include "wuffs-v0.4.c"
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -55,7 +56,7 @@ grow_metadata_buffer(vector<uint8_t> *storage, wuffs_base__io_buffer *dst,
size_t required, Error *error)
{
if (required > kMaxMetadataSize) {
set_error(error, "metadata is too large");
set_error(error, _("metadata is too large"));
return false;
}
size_t size = storage->size();
@@ -80,7 +81,7 @@ pull_passthrough(const wuffs_base__more_information *minfo,
uint64_t pos = src->meta.pos;
if (pos > r.min_incl ||
wuffs_base__u64__sat_sub(r.max_excl, pos) > src->meta.wi) {
set_error(error, "metadata is outside the read buffer");
set_error(error, _("metadata is outside the read buffer"));
return false;
}
const uint64_t length64 = r.max_excl - r.min_incl;
@@ -118,7 +119,7 @@ pull_metadata(wuffs_base__image_decoder *dec, wuffs_base__io_buffer *src,
// Use Wuffs accessor functions in the caller.
break;
default:
set_error(error, "Wuffs metadata API incompatibility");
set_error(error, _("Wuffs metadata API incompatibility"));
return false;
case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH:
@@ -209,7 +210,7 @@ take_reported_metadata(WuffsLoadContext &ctx, Error *error)
switch (wuffs_base__more_information__metadata__fourcc(&minfo)) {
case WUFFS_BASE__FOURCC__EXIF:
if (ctx.have_exif) {
add_warning(*ctx.octx, "ignoring repeated Exif");
add_warning(*ctx.octx, _("ignoring repeated Exif"));
break;
}
ctx.meta_exif = std::move(bytes);
@@ -217,7 +218,7 @@ take_reported_metadata(WuffsLoadContext &ctx, Error *error)
break;
case WUFFS_BASE__FOURCC__ICCP:
if (ctx.have_iccp) {
add_warning(*ctx.octx, "ignoring repeated ICC profile");
add_warning(*ctx.octx, _("ignoring repeated ICC profile"));
break;
}
ctx.meta_iccp = std::move(bytes);
@@ -225,7 +226,7 @@ take_reported_metadata(WuffsLoadContext &ctx, Error *error)
break;
case WUFFS_BASE__FOURCC__XMP:
if (ctx.have_xmp) {
add_warning(*ctx.octx, "ignoring repeated XMP");
add_warning(*ctx.octx, _("ignoring repeated XMP"));
break;
}
ctx.meta_xmp = std::move(bytes);
@@ -278,7 +279,7 @@ load_wuffs_frame_compose(WuffsLoadContext &ctx, ImagePtr &image,
const ImagePtr &prev = ctx.result_tail;
ImagePtr canvas = image_new(prev->width, prev->height);
if (!canvas) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return false;
}
@@ -311,7 +312,7 @@ load_wuffs_frame_compose(WuffsLoadContext &ctx, ImagePtr &image,
WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS) {
ctx.restore_previous = image_new(canvas->width, canvas->height);
if (!ctx.restore_previous) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return false;
}
memcpy(ctx.restore_previous->data.data(), canvas->data.data(),
@@ -324,7 +325,7 @@ load_wuffs_frame_compose(WuffsLoadContext &ctx, ImagePtr &image,
bounds.max_excl_y > bounds.min_incl_y) {
ImagePtr region = crop(*image, bounds);
if (!region) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
return false;
}
BlendOp op = wuffs_base__frame_config__overwrite_instead_of_blend(&fc)
@@ -366,7 +367,7 @@ load_wuffs_frame(WuffsLoadContext &ctx, Error *error)
ImagePtr image = image_new(ctx.width, ctx.height);
if (!image) {
set_error(error, "image allocation failure");
set_error(error, _("image allocation failure"));
ctx.result.reset();
ctx.result_tail.reset();
return false;
@@ -471,14 +472,14 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
// This, at least currently, seems excessive.
if (!wuffs_base__image_config__is_valid(&ctx.cfg)) {
set_error(error, "invalid Wuffs image configuration");
set_error(error, _("invalid Wuffs image configuration"));
return nullptr;
}
ctx.width = wuffs_base__pixel_config__width(&ctx.cfg.pixcfg);
ctx.height = wuffs_base__pixel_config__height(&ctx.cfg.pixcfg);
if (ctx.width == 0 || ctx.height == 0) {
set_error(error, "invalid image dimensions");
set_error(error, _("invalid image dimensions"));
return nullptr;
}
@@ -510,7 +511,7 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
apply_collected_metadata(ctx);
if (!ctx.result && error && error->message.empty())
set_error(error, "no frames decoded");
set_error(error, _("no frames decoded"));
return ctx.result;
}
@@ -521,7 +522,7 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),
unique_ptr<wuffs_base__image_decoder, void (*)(void *)> dec(
allocate(), &free);
if (!dec) {
set_error(error, "memory allocation failed or internal error");
set_error(error, _("memory allocation failed or internal error"));
return nullptr;
}
@@ -604,7 +605,7 @@ detail::load_wuffs(
wuffs_webp__decoder__alloc_as__wuffs_base__image_decoder, data, ctx,
error);
default:
set_error(error, "unsupported or unrecognized Wuffs format");
set_error(error, _("unsupported or unrecognized Wuffs format"));
return nullptr;
}
}
+5 -4
View File
@@ -6,6 +6,7 @@
//
#include <dawn-config.h>
#include <dawn-gettext.h>
#include "libdn-loaders.h"
#include "libdn.h"
@@ -130,7 +131,7 @@ detail::load_xcursor(
span<const uint8_t> data, const OpenContext &ctx, Error *error)
{
if (data.size() > size_t(LONG_MAX)) {
set_error(error, "size overflow");
set_error(error, _("size overflow"));
return nullptr;
}
@@ -144,7 +145,7 @@ detail::load_xcursor(
unique_ptr<XcursorImages, void (*)(XcursorImages *)> images(
XcursorXcFileLoadAllImages(&file.parent), XcursorImagesDestroy);
if (!images) {
set_error(error, "not an Xcursor image");
set_error(error, _("not an Xcursor image"));
return nullptr;
}
@@ -162,7 +163,7 @@ detail::load_xcursor(
ImagePtr frame = load_xcursor_image(src);
if (!frame) {
add_warning(ctx, "image allocation failure");
add_warning(ctx, _("image allocation failure"));
last_size = src->size;
continue;
}
@@ -179,7 +180,7 @@ detail::load_xcursor(
}
if (!pages) {
set_error(error, "empty or unsupported Xcursor image");
set_error(error, _("empty or unsupported Xcursor image"));
return nullptr;
}
+1
View File
@@ -173,6 +173,7 @@ dbsync
fetch $pkg-qt6-base $pkg-vulkan-loader $pkg-vulkan-headers $pkg-libwebp \
$pkg-libjpeg-turbo $pkg-libheif $pkg-libjxl $pkg-openjpeg2 $pkg-libraw \
$pkg-jxrlib $pkg-libwmf $pkg-zlib $pkg-shared-mime-info $pkg-gcc-libs \
$pkg-gettext-runtime \
#$pkg-perl $pkg-perl-win32-api
verify
extract
+103
View File
@@ -0,0 +1,103 @@
# Dawn keeps one message catalogue for the application and the library alike.
# Extraction and compilation run on the host, which is what a Windows cross
# build has; only libintl itself comes from the target.
find_program(XGETTEXT_EXECUTABLE xgettext)
find_program(MSGMERGE_EXECUTABLE msgmerge)
find_program(MSGFMT_EXECUTABLE msgfmt)
# Both lists are read at configure time, so editing one has to re-run it.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS"
"${CMAKE_CURRENT_SOURCE_DIR}/POTFILES")
set(pot "${CMAKE_CURRENT_SOURCE_DIR}/dawn.pot")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS" linguas REGEX "^[^#]")
list(JOIN linguas " " linguas)
separate_arguments(linguas UNIX_COMMAND "${linguas}")
# POTFILES names every platform, so that a Linux machine can still extract
# what only macOS or Windows compiles. xgettext has no scanner for .mm,
# and its C++ one reads Objective-C++ well enough for string literals.
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/POTFILES" potfiles REGEX "^[^#]")
# Desktop entries are a language of their own to xgettext, and their keys
# say what to extract; a source name is never one of them.
set(sources ${potfiles})
list(FILTER sources EXCLUDE REGEX "[.]desktop$")
set(desktops ${potfiles})
list(FILTER desktops INCLUDE REGEX "[.]desktop$")
list(JOIN sources "\n" source_list)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/POTFILES.sources" "${source_list}\n")
# Catalogues live where the rest of the package data does, so that the build
# tree, the bundle and the installation all resolve them the same way.
set(DAWN_LOCALE_DIR "${PROJECT_BINARY_DIR}/share/locale" PARENT_SCOPE)
set(locale_dir "${PROJECT_BINARY_DIR}/share/locale")
file(MAKE_DIRECTORY "${locale_dir}")
if (XGETTEXT_EXECUTABLE AND MSGMERGE_EXECUTABLE)
set(merge_commands)
foreach (lang IN LISTS linguas)
list(APPEND merge_commands COMMAND "${MSGMERGE_EXECUTABLE}"
--quiet --update --backup=none "${lang}.po" "${pot}")
endforeach()
# Both runs write the same header, the second one over the first.
set(about
--sort-by-file
"--package-name=${PROJECT_NAME}"
"--package-version=${PROJECT_VERSION}"
"--copyright-holder=${CPACK_PACKAGE_VENDOR}"
"--msgid-bugs-address=${PROJECT_HOMEPAGE_URL}"
"--directory=${PROJECT_SOURCE_DIR}"
"--output=${pot}")
add_custom_target(update-po
COMMAND "${XGETTEXT_EXECUTABLE}"
--language=C++ --from-code=UTF-8 --qt
--keyword=_ --keyword=N_ --keyword=C_:1c,2 --keyword=P_:1,2
--add-comments=TRANSLATORS ${about}
"--files-from=${CMAKE_CURRENT_BINARY_DIR}/POTFILES.sources"
COMMAND "${XGETTEXT_EXECUTABLE}"
--language=Desktop --join-existing
--keyword --keyword=GenericName --keyword=X-GNOME-FullName
${about} ${desktops}
${merge_commands}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Extracting messages and updating translations"
VERBATIM)
endif()
# Ordinary builds only compile what translators have already committed.
set(catalogues)
foreach (lang IN LISTS linguas)
set(mo "${locale_dir}/${lang}/LC_MESSAGES/dawn.mo")
if (NOT MSGFMT_EXECUTABLE)
message(FATAL_ERROR "msgfmt is needed to compile ${lang}.po")
endif()
add_custom_command(OUTPUT "${mo}"
COMMAND "${CMAKE_COMMAND}" -E make_directory
"${locale_dir}/${lang}/LC_MESSAGES"
COMMAND "${MSGFMT_EXECUTABLE}" --check
-o "${mo}" "${CMAKE_CURRENT_SOURCE_DIR}/${lang}.po"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${lang}.po"
COMMENT "Compiling ${lang}.po"
VERBATIM)
list(APPEND catalogues "${mo}")
endforeach()
set(DAWN_CATALOGUES ${catalogues} PARENT_SCOPE)
# Desktop entries take their translations from the PO files directly.
set(translations ${linguas})
list(TRANSFORM translations PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/")
list(TRANSFORM translations APPEND ".po")
set(DAWN_TRANSLATIONS ${translations} PARENT_SCOPE)
add_custom_target(catalogues ALL DEPENDS ${catalogues})
# The macOS bundle carries its own copy, put there by the dn target.
if (WIN32)
install(DIRECTORY "${locale_dir}/" DESTINATION share/locale)
elseif (NOT APPLE)
install(DIRECTORY "${locale_dir}/"
DESTINATION "${CMAKE_INSTALL_LOCALEDIR}")
endif()
+2
View File
@@ -0,0 +1,2 @@
# Languages with a committed translation, whitespace-separated.
# Add one with: msginit --locale=LL --input=dawn.pot
+41
View File
@@ -0,0 +1,41 @@
# Every source that holds a translatable string, listed for every platform:
# extraction must not depend on what the machine running it can compile.
# Paths are relative to the top of the repository.
dn/action.cpp
dn/app-menu-macos.mm
dn/app.cpp
dn/kit-browser.cpp
dn/kit-chrome.cpp
dn/kit-cie-diagram.cpp
dn/kit-viewer.cpp
dn/main.cpp
dn/name.janouch.Dawn.Browse.desktop
dn/name.janouch.Dawn.Commander.desktop
dn/name.janouch.Dawn.CropJpeg.desktop
dn/name.janouch.Dawn.desktop
dn/window.cpp
libdn/config-macos.mm
libdn/config-unix.cpp
libdn/config-windows.cpp
libdn/libdn.cpp
libdn/load-cgpdf.mm
libdn/load-dnrs.cpp
libdn/load-heif.cpp
libdn/load-icns.cpp
libdn/load-imageio.mm
libdn/load-jpeg.cpp
libdn/load-jxl.cpp
libdn/load-jxr.cpp
libdn/load-libraw.cpp
libdn/load-librsvg.cpp
libdn/load-libwmf.cpp
libdn/load-openjpeg.cpp
libdn/load-ora.cpp
libdn/load-poppler.cpp
libdn/load-psd.cpp
libdn/load-resvg.cpp
libdn/load-tiff-ep.cpp
libdn/load-tiff.cpp
libdn/load-webp.cpp
libdn/load-wuffs.cpp
libdn/load-xcursor.cpp
+32
View File
@@ -130,6 +130,38 @@ target_link_libraries(test-thumbnailer PRIVATE
PkgConfig::WEBPMUX)
add_test(NAME thumbnailer COMMAND test-thumbnailer)
# Translation needs a catalogue to find, and gettext settles the language
# once per process, so each case gets a process of its own.
if (MSGFMT_EXECUTABLE)
set(gettext_locale_dir "${CMAKE_CURRENT_BINARY_DIR}/locale")
set(gettext_mo "${gettext_locale_dir}/xx/LC_MESSAGES/dawn.mo")
add_custom_command(OUTPUT "${gettext_mo}"
COMMAND "${CMAKE_COMMAND}" -E make_directory
"${gettext_locale_dir}/xx/LC_MESSAGES"
COMMAND "${MSGFMT_EXECUTABLE}" --check -o "${gettext_mo}"
"${CMAKE_CURRENT_SOURCE_DIR}/test-gettext.po"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test-gettext.po"
COMMENT "Compiling the test catalogue"
VERBATIM)
add_executable(test-gettext test-gettext.cpp "${gettext_mo}")
target_link_libraries(test-gettext PRIVATE dawn::libdn)
target_compile_definitions(test-gettext PRIVATE
DAWN_TEST_LOCALE_DIR="${gettext_locale_dir}")
add_test(NAME gettext_fallback COMMAND test-gettext fallback)
set_tests_properties(gettext_fallback PROPERTIES ENVIRONMENT "LANGUAGE=")
foreach (case translated context plural library)
add_test(NAME gettext_${case} COMMAND test-gettext ${case})
set_tests_properties(gettext_${case} PROPERTIES ENVIRONMENT
"LANGUAGE=xx")
endforeach()
# A system with no locale but "C" leaves gettext nothing to select with.
set_tests_properties(gettext_fallback gettext_translated gettext_context
gettext_plural gettext_library PROPERTIES SKIP_RETURN_CODE 77)
endif()
add_executable(test-lxdr test-lxdr.cpp)
target_link_libraries(test-lxdr PRIVATE dawn::libdn)
add_test(NAME lxdr COMMAND test-lxdr)
+140
View File
@@ -0,0 +1,140 @@
//
// test-gettext.cpp: message catalogue lookup
//
// Copyright The Dawn Authors
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "libdn/libdn.h"
#include "test.hpp"
#include <clocale>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
using namespace std;
// CTest reads this as "this case had nothing to run against".
constexpr int kSkip = 77;
// gettext disables LANGUAGE under a "C" locale, which "C.UTF-8" counts as.
static bool
is_c_locale(const char *name)
{
return name[0] == 'C' && (!name[1] || name[1] == '.');
}
// So a real locale has to be found first. Which one it is does not matter:
// LANGUAGE picks the catalogue, and the codeset binding settles the encoding.
// Both the environment and the C library get told, because glibc reads the
// locale it has while GNU libintl reads the variable.
static bool
adopt_any_locale()
{
// What the machine is already set up for, first; the rest are names
// generated often enough that one of them tends to exist.
const char *current = setlocale(LC_MESSAGES, "");
for (const char *name : {current, "en_US.UTF-8", "en_US.utf8", "en_US",
"en_GB.UTF-8", "de_DE.UTF-8", "fr_FR.UTF-8"}) {
if (!name || !*name || is_c_locale(name) || !setlocale(LC_ALL, name))
continue;
#ifdef _WIN32
_putenv_s("LC_ALL", name);
#else
setenv("LC_ALL", name, 1);
#endif
return true;
}
return false;
}
static void
test_fallback()
{
CHECK(!strcmp(_("Dawn"), "Dawn"));
CHECK(!strcmp(C_("verb", "Mirror"), "Mirror"));
CHECK(!strcmp(P_("%d file", "%d files", 1), "%d file"));
CHECK(!strcmp(P_("%d file", "%d files", 3), "%d files"));
}
static void
test_translated()
{
CHECK(!strcmp(_("Dawn"), "Úsvit"));
// What no translator has got to yet stays readable.
CHECK(!strcmp(_("Not in the catalogue"), "Not in the catalogue"));
}
static void
test_context()
{
CHECK(!strcmp(C_("verb", "Mirror"), "zrcadlit"));
CHECK(!strcmp(C_("noun", "Mirror"), "zrcadlo"));
// A context only ever narrows: without one there is nothing to find.
CHECK(!strcmp(_("Mirror"), "Mirror"));
}
static void
test_plural()
{
CHECK(!strcmp(P_("%d file", "%d files", 1), "%d soubor"));
CHECK(!strcmp(P_("%d file", "%d files", 3), "%d soubory"));
CHECK(!strcmp(P_("%d file", "%d files", 7), "%d souborů"));
}
// libdn speaks through the same catalogue, without having chosen it.
static void
test_library_error()
{
const uint8_t garbage[] = {0xde, 0xad, 0xbe, 0xef, 0, 1, 2, 3};
// One loader, so that what is left over is its own diagnostic, and one
// this build is certain to have.
const string only[] = {"ICNS"};
dawn::OpenContext ctx;
ctx.loaders = only;
dawn::Error error;
CHECK(!dawn::open_from_data(garbage, ctx, &error));
CHECK(error.message == "toto není obrázek ICNS");
}
int
main(int argc, char **argv)
{
// Language selection is process-wide and settled by the first lookup,
// so every case runs as its own process, told which one it is.
const string_view name = argc > 1 ? argv[1] : "";
// Without a locale there is no catalogue to consult, and only the case
// that expects none can still say anything.
if (!adopt_any_locale() && name != "fallback") {
fprintf(stderr, "%s: this system has no locale but \"C\"\n", argv[0]);
return kSkip;
}
dawn::gettext_init();
// The package catalogues gettext_init() bound are of no use here.
bindtextdomain(DAWN_TEXTDOMAIN, DAWN_TEST_LOCALE_DIR);
for (const test::Case &c : {
test::Case{"fallback", test_fallback},
test::Case{"translated", test_translated},
test::Case{"context", test_context},
test::Case{"plural", test_plural},
test::Case{"library", test_library_error},
}) {
if (name == c.name)
return test::run({c});
}
fprintf(stderr, "usage: %s {fallback|translated|context|plural|library}\n",
argv[0]);
return 1;
}
+37
View File
@@ -0,0 +1,37 @@
# Test catalogue: deliberately not a translation of anything, never installed.
# The plural rules are Czech's, to exercise a language with three forms.
#
# Copyright The Dawn Authors
# SPDX-License-Identifier: MPL-2.0
#
msgid ""
msgstr ""
"Project-Id-Version: Dawn\n"
"PO-Revision-Date: 2026-09-12 00:00+0000\n"
"Last-Translator: The Dawn Authors\n"
"Language-Team: none\n"
"Language: xx\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
msgid "Dawn"
msgstr "Úsvit"
msgctxt "verb"
msgid "Mirror"
msgstr "zrcadlit"
msgctxt "noun"
msgid "Mirror"
msgstr "zrcadlo"
msgid "%d file"
msgid_plural "%d files"
msgstr[0] "%d soubor"
msgstr[1] "%d soubory"
msgstr[2] "%d souborů"
msgid "not an ICNS image"
msgstr "toto není obrázek ICNS"
+4
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "ipc-rpc.hpp"
#include "ipc-shm.hpp"
#include "ipc/imaged.lxdr.hpp"
@@ -41,6 +43,8 @@ receive(dawn::ipc::Channel &channel, proto::FrameView &view,
int
main()
{
dawn::gettext_init();
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
+4
View File
@@ -5,6 +5,8 @@
// SPDX-License-Identifier: MPL-2.0
//
#include <dawn-gettext.h>
#include "ipc-rpc.hpp"
#include "ipc-shm.hpp"
#include "ipc/thumbd.lxdr.hpp"
@@ -54,6 +56,8 @@ receive(dawn::ipc::Channel &channel, proto::FrameView &view,
int
main(int argc, char **argv)
{
dawn::gettext_init();
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);