Translations
This commit is contained in:
@@ -3,5 +3,6 @@
|
||||
/build/
|
||||
/build-*/
|
||||
/libdnrs/Cargo.lock
|
||||
/po/dawn.pot
|
||||
compile_commands.json
|
||||
.cache/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 §ion : 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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(¶meters);
|
||||
if (!opj_setup_decoder(ctx.codec, ¶meters)) {
|
||||
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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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, ×tamp)) {
|
||||
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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -0,0 +1,2 @@
|
||||
# Languages with a committed translation, whitespace-separated.
|
||||
# Add one with: msginit --locale=LL --input=dawn.pot
|
||||
+41
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user