Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ jobs:
with:
pkgs: boost-json cpr gettext-libintl glib gtest libsecret maddy
triplet: ${{ matrix.variant.triplet }}
revision: 3c81ed09705008f13bf76f39853507bef51f71a1
revision: 4103f46cb1ebb69ef5511cab840ecea0f441fbd7
token: ${{ github.token }}
cache-key: ${{ matrix.variant.triplet }}
cache-key: ${{ matrix.variant.triplet }}-4103f46cb1ebb69ef5511cab840ecea0f441fbd7
- name: "Build"
working-directory: ${{github.workspace}}/build
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ jobs:
with:
pkgs: boost-json cpr gettext-libintl glib gtest maddy
triplet: arm64-osx
revision: 3c81ed09705008f13bf76f39853507bef51f71a1
revision: 4103f46cb1ebb69ef5511cab840ecea0f441fbd7
token: ${{ github.token }}
cache-key: "arm64-osx"
cache-key: "arm64-osx-4103f46cb1ebb69ef5511cab840ecea0f441fbd7"
- name: "Build"
working-directory: ${{github.workspace}}/build
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ jobs:
with:
pkgs: boost-json cpr gettext-libintl gtest maddy sqlcipher
triplet: ${{ matrix.variant.triplet }}
revision: 3c81ed09705008f13bf76f39853507bef51f71a1
revision: 4103f46cb1ebb69ef5511cab840ecea0f441fbd7
token: ${{ github.token }}
cache-key: ${{ matrix.variant.triplet }}
cache-key: ${{ matrix.variant.triplet }}-4103f46cb1ebb69ef5511cab840ecea0f441fbd7
- name: "Build"
working-directory: ${{github.workspace}}/build
run: |
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 2025.7.4
### Breaking Changes
- `maddy` dependency requires >= 1.6.0
### New APIs
#### App
- Added `CancellationToken` class
### Fixes
#### Localization
- Fixed included headers
- Fixed Gettext::changeLanguage("C") not turning off translations

## 2025.7.3
### Breaking Changes
None
Expand Down
11 changes: 5 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")

#libnick Definition
project ("libnick" LANGUAGES C CXX VERSION 2025.7.3 DESCRIPTION "A cross-platform base for native Nickvision applications.")
project ("libnick" LANGUAGES C CXX VERSION 2025.7.4 DESCRIPTION "A cross-platform base for native Nickvision applications.")
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
include(CTest)
Expand All @@ -36,6 +36,7 @@ if(NOT WIN32)
endif()
add_library (${PROJECT_NAME}
"include/app/appinfo.h"
"include/app/cancellationtoken.h"
"include/app/datafilebase.h"
"include/app/datafilemanager.h"
"include/app/windowgeometry.h"
Expand Down Expand Up @@ -95,6 +96,7 @@ add_library (${PROJECT_NAME}
"include/update/version.h"
"include/update/versiontype.h"
"src/app/appinfo.cpp"
"src/app/cancellationtoken.cpp"
"src/app/datafilebase.cpp"
"src/app/datafilemanager.cpp"
"src/app/windowgeometry.cpp"
Expand Down Expand Up @@ -146,11 +148,8 @@ endif()
find_package(Boost REQUIRED COMPONENTS json)
find_package(cpr CONFIG REQUIRED)
find_package(Intl REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::json cpr::cpr Intl::Intl)
if(USING_VCPKG)
find_package(unofficial-maddy CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE unofficial::maddy::maddy)
endif()
find_package(maddy CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::json cpr::cpr Intl::Intl maddy::maddy)
if(WIN32)
find_package(sqlcipher CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC sqlcipher::sqlcipher Advapi32 Dnsapi Dwmapi Gdiplus Kernel32 Shell32 UxTheme Ws2_32)
Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ PROJECT_NAME = "libnick"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = "2025.7.3"
PROJECT_NUMBER = "2025.7.4"

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
Expand Down
4 changes: 1 addition & 3 deletions cmake/config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ endif()
find_dependency(Boost REQUIRED COMPONENTS json)
find_dependency(cpr CONFIG REQUIRED)
find_dependency(Intl REQUIRED)
if(USING_VCPKG)
find_dependency(unofficial-maddy REQUIRED)
endif()
find_dependency(maddy CONFIG REQUIRED)
if(WIN32)
find_dependency(sqlcipher CONFIG REQUIRED)
elseif(APPLE)
Expand Down
52 changes: 52 additions & 0 deletions include/app/cancellationtoken.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef CANCELLATIONTOKEN_H
#define CANCELLATIONTOKEN_H

#include <functional>
#include <mutex>

namespace Nickvision::App
{
/**
* @brief A token that can be used to cancel an operation.
*/
class CancellationToken
{
public:
/**
* @brief Constructs a CancellationToken.
* @param cancelFunction A callback function to call when the token is cancelled
*/
CancellationToken(const std::function<void()>& cancelFunction = {});
/**
* @brief Gets whether or not the token is cancelled.
* @return True if token is cancelled, else false
*/
bool isCancelled() const;
/**
* @brief Gets the cancel function to be called when the token is cancelled.
* @return The cancel function
*/
const std::function<void()>& getCancelFunction() const;
/**
* @brief Sets the cancel function to be called when the token is cancelled.
* @param cancelFunction The cancel function
*/
void setCancelFunction(const std::function<void()>& cancelFunction);
/**
* @brief Cancels the token.
*/
void cancel();
/**
* @brief Converts the token to a boolean.
* @return True if token is cancelled, else false
*/
operator bool() const;

private:
mutable std::mutex m_mutex;
bool m_cancelled;
std::function<void()> m_cancelFunction;
};
}

#endif //CANCELLATIONTOKEN_H
5 changes: 4 additions & 1 deletion include/events/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ namespace Nickvision::Events
std::lock_guard<std::mutex> lock{ m_mutex };
for (const std::function<void(const T&)>& handler : m_handlers)
{
handler(param);
if(handler)
{
handler(param);
}
}
}
/**
Expand Down
26 changes: 21 additions & 5 deletions include/localization/gettext.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
#include <libintl.h>

#define GETTEXT_CONTEXT_SEPARATOR "\004"
#define _(String) dgettext(::Nickvision::Localization::Gettext::getDomainName().c_str(), String)
#define _n(String, StringPlural, N) dngettext(::Nickvision::Localization::Gettext::getDomainName().c_str(), String, StringPlural, static_cast<unsigned long>(N))
#define _(String) ::Nickvision::Localization::Gettext::dgettext(String)
#define _n(String, StringPlural, N) ::Nickvision::Localization::Gettext::dngettext(String, StringPlural, static_cast<unsigned long>(N))
#define _f(String, ...) ::Nickvision::Localization::Gettext::fgettext(String, __VA_ARGS__)
#define _fn(String, StringPlural, N, ...) ::Nickvision::Localization::Gettext::fngettext(String, StringPlural, static_cast<unsigned long>(N), __VA_ARGS__)
#define _p(Context, String) ::Nickvision::Localization::Gettext::pgettext(Context GETTEXT_CONTEXT_SEPARATOR String, String)
Expand All @@ -57,31 +57,47 @@ namespace Nickvision::Localization::Gettext
const std::vector<std::string>& getAvailableLanguages();
/**
* @brief Changes the current language for gettext translations.
* @param language The language code to change translations to (use "C" to turn off translations and use the default language)
* @param language The language code to change translations to (use "C" to turn off translations; use "" to use the system default language)
* @return True if the language was changed successfully, else false
*/
bool changeLanguage(const std::string& language);
/**
* @brief Translates a message.
* @param msgid The message to translate
* @return The translated message
*/
const char* dgettext(const char* msgid);
/**
* @brief Translates a plural message.
* @param msg The message to translate
* @param msgPlural The plural version of the message to translate
* @param n The number of objects (used to determine whether or not to use the plural version of the message)
* @return The translated message for the given number of objects
*/
const char* dngettext(const char* msg, const char* msgPlural, unsigned long n);
/**
* @brief Translates a message and formats it with the given arguments.
* @param msg The message to translate
* @param args The arguments to format the translated message with
* @return The formatted translated message
*/
template<typename... Args>
std::string fgettext(const char* msg, Args&&... args)
{
return std::vformat(_(msg), std::make_format_args(args...));
return std::vformat(Nickvision::Localization::Gettext::dgettext(msg), std::make_format_args(args...));
}
/**
* @brief Translates a plural message and formats it with the given arguments.
* @param msg The message to translate
* @param msgPlural The plural version of the message to translate
* @param n The number of objects (used to determine whether or not to use the plural version of the message)
* @param args The arguments to format the translated message with
* @return The formatted translated message for the given number of objects
*/
template<typename... Args>
std::string fngettext(const char* msg, const char* msgPlural, unsigned long n, Args&&... args)
{
return std::vformat(_n(msg, msgPlural, n), std::make_format_args(args...));
return std::vformat(Nickvision::Localization::Gettext::dngettext(msg, msgPlural, n), std::make_format_args(args...));
}
/**
* @brief Translates a message for a given context.
Expand Down
11 changes: 6 additions & 5 deletions manual/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@

libnick provides Nickvision apps with a common set of cross-platform APIs for managing system and desktop app functionality such as network management, taskbar icons, translations, app updates, and more.

## 2025.7.3
## 2025.7.4
### Breaking Changes
None
- `maddy` dependency requires >= 1.6.0
### New APIs
#### Localization
- You can now specify "C" in the `Gettext::changeLanguage()` function to turn off translations.
#### App
- Added `CancellationToken` class
### Fixes
#### Localization
- Improved `Gettext::getAvailableLanguages()`'s search for languages
- Fixed included headers
- Fixed Gettext::changeLanguage("C") not turning off translations

## Dependencies
The following are a list of dependencies used by libnick.
Expand Down
50 changes: 50 additions & 0 deletions src/app/cancellationtoken.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "app/cancellationtoken.h"

namespace Nickvision::App
{
CancellationToken::CancellationToken(const std::function<void()>& cancelFunction)
: m_cancelled{ false },
m_cancelFunction{ cancelFunction }
{

}

bool CancellationToken::isCancelled() const
{
std::lock_guard<std::mutex> lock{ m_mutex };
return m_cancelled;
}

const std::function<void()>& CancellationToken::getCancelFunction() const
{
std::lock_guard<std::mutex> lock{ m_mutex };
return m_cancelFunction;
}

void CancellationToken::setCancelFunction(const std::function<void()>& cancelFunction)
{
std::lock_guard<std::mutex> lock{ m_mutex };
m_cancelFunction = cancelFunction;
}

void CancellationToken::cancel()
{
std::unique_lock<std::mutex> lock{ m_mutex };
if(m_cancelled)
{
return;
}
m_cancelled = true;
if(m_cancelFunction)
{
lock.unlock();
m_cancelFunction();
}
}

CancellationToken::operator bool() const
{
std::lock_guard<std::mutex> lock{ m_mutex };
return m_cancelled;
}
}
48 changes: 42 additions & 6 deletions src/localization/gettext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ using namespace Nickvision::System;

namespace Nickvision::Localization
{
static std::string s_domainName;
static std::string s_domainName{};
static bool s_translationsOff{ false };

bool Gettext::init(const std::string& domainName)
{
Expand Down Expand Up @@ -57,10 +58,18 @@ namespace Nickvision::Localization

bool Gettext::changeLanguage(const std::string& language)
{
if(language == "C")
if(language.empty())
{
Environment::setVariable("LANGUAGE", "");
setlocale(LC_ALL, "C");
if(Environment::hasVariable("LANGUAGE"))
{
Environment::clearVariable("LANGUAGE");
}
s_translationsOff = false;
return true;
}
else if(language == "C")
{
s_translationsOff = true;
return true;
}
const std::vector<std::string>& langs{ Gettext::getAvailableLanguages() };
Expand All @@ -69,12 +78,35 @@ namespace Nickvision::Localization
return false;
}
Environment::setVariable("LANGUAGE", language);
s_translationsOff = false;
return true;
}

const char* Gettext::dgettext(const char* msgid)
{
if(s_translationsOff)
{
return msgid;
}
return ::dgettext(s_domainName.c_str(), msgid);
}

const char* Gettext::dngettext(const char* msg, const char* msgPlural, unsigned long n)
{
if(s_translationsOff)
{
return n == 1 ? msg : msgPlural;
}
return ::dngettext(s_domainName.c_str(), msg, msgPlural, n);
}

const char* Gettext::pgettext(const char* context, const char* msg)
{
const char* translation{ dcgettext(s_domainName.c_str(), context, LC_MESSAGES) };
if(s_translationsOff)
{
return msg;
}
const char* translation{ ::dcgettext(s_domainName.c_str(), context, LC_MESSAGES) };
if (translation == context)
{
return msg;
Expand All @@ -84,7 +116,11 @@ namespace Nickvision::Localization

const char* Gettext::pngettext(const char* context, const char* msg, const char* msgPlural, unsigned long n)
{
const char* translation{ dcngettext(s_domainName.c_str(), context, msgPlural, n, LC_MESSAGES) };
if(s_translationsOff)
{
return n == 1 ? msg : msgPlural;
}
const char* translation{ ::dcngettext(s_domainName.c_str(), context, msgPlural, n, LC_MESSAGES) };
if (translation == context || translation == msgPlural)
{
return n == 1 ? msg : msgPlural;
Expand Down
Loading