Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(i18n)
add_subdirectory(localedata)
if (TARGET Qt6::Qml)
# add_subdirectory(i18n-qml)
# add_subdirectory(localedata-qml)
endif()
ecm_qt_install_logging_categories(
EXPORT KI18N
FILE ki18n.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
@@ -0,0 +1,61 @@
# SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
add_library(KF6I18nQml)
add_library(KF6::I18nQml ALIAS KF6I18nQml)
set_target_properties(KF6I18nQml PROPERTIES
VERSION ${KI18N_VERSION}
SOVERSION ${KI18N_SOVERSION}
EXPORT_NAME I18nQml
)
target_sources(KF6I18nQml PRIVATE
klocalizedqmlcontext.cpp
)
ecm_qt_declare_logging_category(KF6I18nQml
HEADER ki18n_qml_logging.h
IDENTIFIER KI18N
CATEGORY_NAME kf.i18n.qml
DESCRIPTION "KI18n QML Integration"
EXPORT KI18N
)
ecm_generate_export_header(KF6I18nQml
BASE_NAME KI18nQml
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KI18n
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6I18nQml
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KI18n>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>" # for version header
)
target_link_libraries(KF6I18nQml
PUBLIC
Qt6::Core
PRIVATE
KF6I18n
Qt6::Qml
)
ecm_generate_headers(KI18n_HEADERS
HEADER_NAMES
KLocalizedQmlContext
REQUIRED_HEADERS KI18n_HEADERS
)
install(TARGETS KF6I18nQml EXPORT KF6I18nTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${KI18n_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/ki18nqml_export.h
DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF}/KI18n" COMPONENT Devel
)
@@ -0,0 +1,634 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// Undefine this because we don't want our i18n*() method names to be turned into i18nd*()
#undef TRANSLATION_DOMAIN
#include "klocalizedqmlcontext.h"
#include <klocalizedstring.h>
#include <QCoreApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include "ki18n_qml_logging.h"
class KLocalizedQmlContextPrivate
{
public:
void markCurrentFunctionAsTranslationBinding(const KLocalizedQmlContext *q) const;
QString m_translationDomain;
};
void KLocalizedQmlContextPrivate::markCurrentFunctionAsTranslationBinding(const KLocalizedQmlContext *q) const
{
#if QT_VERSION > QT_VERSION_CHECK(6, 6, 0)
if (auto engine = qmlEngine(q); engine) {
engine->markCurrentFunctionAsTranslationBinding();
} else {
qCDebug(KI18N) << "No QML engine available, KLocalizedQmlContext not properly set up?";
}
#endif
}
KLocalizedQmlContext::KLocalizedQmlContext(QObject *parent)
: QObject(parent)
, d(new KLocalizedQmlContextPrivate)
{
QCoreApplication::instance()->installEventFilter(this);
}
KLocalizedQmlContext::~KLocalizedQmlContext() = default;
QString KLocalizedQmlContext::translationDomain() const
{
return d->m_translationDomain;
}
void KLocalizedQmlContext::setTranslationDomain(const QString &domain)
{
if (domain != d->m_translationDomain) {
d->m_translationDomain = domain;
Q_EMIT translationDomainChanged(domain);
}
}
static void subsVariant(KLocalizedString &trMessage, const QVariant &value)
{
switch (value.userType()) {
case QMetaType::QString:
trMessage = trMessage.subs(value.toString());
break;
case QMetaType::Int:
trMessage = trMessage.subs(value.toInt());
break;
case QMetaType::Double:
trMessage = trMessage.subs(value.toDouble());
break;
case QMetaType::Char:
trMessage = trMessage.subs(value.toChar());
break;
default:
if (value.canConvert<QString>()) {
trMessage = trMessage.subs(value.toString());
} else {
trMessage = trMessage.subs(QStringLiteral("???"));
qCWarning(KI18N) << "couldn't convert" << value << "to translate";
}
}
}
static void resolveMessage(KLocalizedString &trMessage,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10 = QVariant())
{
if (param1.isValid()) {
subsVariant(trMessage, param1);
}
if (param2.isValid()) {
subsVariant(trMessage, param2);
}
if (param3.isValid()) {
subsVariant(trMessage, param3);
}
if (param4.isValid()) {
subsVariant(trMessage, param4);
}
if (param5.isValid()) {
subsVariant(trMessage, param5);
}
if (param6.isValid()) {
subsVariant(trMessage, param6);
}
if (param7.isValid()) {
subsVariant(trMessage, param7);
}
if (param8.isValid()) {
subsVariant(trMessage, param8);
}
if (param9.isValid()) {
subsVariant(trMessage, param9);
}
if (param10.isValid()) {
subsVariant(trMessage, param10);
}
}
static void resolvePlural(KLocalizedString &trMessage, const QVariant &param)
{
trMessage = trMessage.subs(param.toInt());
}
QString KLocalizedQmlContext::i18n(const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (message.isEmpty()) {
qCWarning(KI18N) << "i18n() needs at least one parameter";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = ki18n(message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18nc(const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "i18nc(\"" << context << message << "\") needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = ki18nc(context.toUtf8().constData(), message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18np(const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "i18np(\"" << singular << plural << "\") needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = ki18np(singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ncp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage =
ki18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = ki18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18nd(const QString &domain,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || message.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "i18nd(\"" << domain << message << "\") needs at least two parameters";
return QString();
}
KLocalizedString trMessage = ki18nd(domain.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "i18ndc() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = ki18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ndp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = ki18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::i18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ndcp() needs at least four arguments";
return QString();
}
KLocalizedString trMessage =
ki18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
/////////////////////////
QString KLocalizedQmlContext::xi18n(const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (message.isEmpty()) {
qCWarning(KI18N) << "xi18n() needs at least one parameter";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = kxi18n(message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18nc(const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "xi18nc(\"" << context << message << "\") needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = kxi18nc(context.toUtf8().constData(), message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18np(const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "xi18np(\"" << singular << plural << "\") needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = kxi18np(singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ncp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage =
kxi18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = kxi18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18nd(const QString &domain,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || message.isEmpty()) {
qCWarning(KI18N).noquote().nospace() << "xi18nd(\"" << domain << message << "\") needs at least two parameters";
return QString();
}
KLocalizedString trMessage = kxi18nd(domain.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "x18ndc() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = kxi18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ndp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = kxi18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
QString KLocalizedQmlContext::xi18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ndcp() needs at least four arguments";
return QString();
}
KLocalizedString trMessage =
kxi18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
d->markCurrentFunctionAsTranslationBinding(this);
return trMessage.toString();
}
bool KLocalizedQmlContext::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::LanguageChange && watched == QCoreApplication::instance()) {
qCDebug(KI18N) << "triggering binding reevaluation";
// run this deferred so we can be sure other things have reacted, such as KLocalizedString
// having updated its internal caches
if (auto engine = qmlEngine(this); engine) {
QMetaObject::invokeMethod(engine, &QQmlEngine::retranslate, Qt::QueuedConnection);
}
}
return QObject::eventFilter(watched, event);
}
KLocalizedQmlContext *KLocalization::Internal::createLocalizedContext(QQmlEngine *engine)
{
auto ctx = new KLocalizedQmlContext(engine);
engine->rootContext()->setContextObject(ctx);
QQmlEngine::setContextForObject(ctx, engine->rootContext());
return ctx;
}
#include "moc_klocalizedqmlcontext.cpp"
@@ -0,0 +1,326 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLOCALIZEDQMLCONTEXT_H
#define KLOCALIZEDQMLCONTEXT_H
#include <ki18nqml_export.h>
#include <QObject>
#include <QVariant>
#include <memory>
class QQmlEngine;
class KLocalizedQmlContextPrivate;
/**
* @class KLocalizedQmlContext klocalizedcontext.h <KLocalizedQmlContext>
*
* This class is meant to be used to simplify integration of the KI18n framework
* in QML.
*
* The way to do so, is by creating this object and setting it as a context
* object:
*
* @code
* QQmlApplicationEngine engine;
* auto ctx = new KLocalizedQmlContext(&engine);
* engine->rootContext()->setContextObject(ctx);
* QQmlEngine::setContextForObject(ctx, engine.rootContext());
* ctx->setTranslationDomain(...);
* @endcode
*
* In many cases this can be simplified using KLocalization::setupLocalizedContext():
* @code
* QQmlApplicationEngine engine;
* KLocalization::setupLocalizedContext(&engine);
* @endcode
*
* Then i18n*() and xi18n*() functions should be available for use from the code
* loaded in the engine, for the view.
*
* Unlike its predecessor KLocalizedContext this does automatically trigger
* a binding re-evaluation when the application language is changed at runtime
* (with Qt 6.6 or higher).
*
* @note Plural functions differ from the C/C++ version. On QML/JS we can get a
* real value easily. To solve warnings on those cases we'll cast the first argument
* to make sure it's taken into account for the plural.
*
* @since 6.8
*/
class KI18NQML_EXPORT KLocalizedQmlContext : public QObject
{
Q_OBJECT
/**
* This property only needs to be specified if the context is being run on a library.
* in an application there is no need to set the translation domain as the application's
* domain can be used.
*/
Q_PROPERTY(QString translationDomain READ translationDomain WRITE setTranslationDomain NOTIFY translationDomainChanged)
public:
explicit KLocalizedQmlContext(QObject *parent = nullptr);
~KLocalizedQmlContext() override;
QString translationDomain() const;
void setTranslationDomain(const QString &domain);
Q_INVOKABLE QString i18n(const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18nc(const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18np(const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18nd(const QString &domain,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18n(const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18nc(const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18np(const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18nd(const QString &domain,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_SIGNALS:
void translationDomainChanged(const QString &translationDomain);
private:
bool eventFilter(QObject *watched, QEvent *event) override;
std::unique_ptr<KLocalizedQmlContextPrivate> const d;
};
namespace KLocalization
{
///@cond internal
namespace Internal
{
[[nodiscard]] KI18NQML_EXPORT KLocalizedQmlContext *createLocalizedContext(QQmlEngine *engine);
}
///@endcond
/** Creates a KLocalizedQmlContext engine and sets it up in the
* root context of @p engine.
*
* If @p TRANSLATION_DOMAIN is defined, that is also set on
* the created context.
*
* @since 6.8
*/
inline KLocalizedQmlContext *setupLocalizedContext(QQmlEngine *engine)
{
auto ctx = Internal::createLocalizedContext(engine);
#ifdef TRANSLATION_DOMAIN
ctx->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
#endif
return ctx;
}
}
#endif
@@ -0,0 +1,140 @@
if (NOT BUILD_SHARED_LIBS AND BUILD_WITH_QML)
set(HAVE_STATIC_KTRANSCRIPT ON)
else()
set(HAVE_STATIC_KTRANSCRIPT OFF)
endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h")
add_library(KF6I18n)
add_library(KF6::I18n ALIAS KF6I18n)
set_target_properties(KF6I18n PROPERTIES
VERSION ${KI18N_VERSION}
SOVERSION ${KI18N_SOVERSION}
EXPORT_NAME I18n
)
target_sources(KF6I18n PRIVATE
klocalizedstring.cpp
klocalizedtranslator.cpp
kcatalog.cpp
kuitsetup.cpp
common_helpers.cpp
klocalizedcontext.cpp
main.cpp
klocalization.cpp
)
ecm_qt_declare_logging_category(KF6I18n
HEADER ki18n_logging.h
IDENTIFIER KI18N
CATEGORY_NAME kf.i18n
OLD_CATEGORY_NAMES kf5.ki18n
DESCRIPTION "KI18n"
EXPORT KI18N
)
ecm_qt_declare_logging_category(KF6I18n
HEADER ki18n_logging_kuit.h
IDENTIFIER KI18N_KUIT
CATEGORY_NAME kf.i18n.kuit
OLD_CATEGORY_NAMES kf5.ki18n.kuit
DESCRIPTION "KI18n KUIT"
EXPORT KI18N
)
ecm_generate_export_header(KF6I18n
BASE_NAME KI18n
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 6.8
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6I18n
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KI18n>"
PUBLIC "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>" # for version header
PRIVATE ${LibIntl_INCLUDE_DIRS}
)
target_link_libraries(KF6I18n PUBLIC Qt6::Core)
# This is only required for platforms which don't use glibc (with glibc LibIntl_LIBRARIES will be empty)
target_link_libraries(KF6I18n PRIVATE ${LibIntl_LIBRARIES})
if (ANDROID)
target_link_libraries(KF6I18n PRIVATE android)
endif()
target_compile_options(KF6I18n PRIVATE -DTRANSLATION_DOMAIN=\"ki18n6\")
install(TARGETS KF6I18n EXPORT KF6I18nTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
ecm_generate_headers(KI18n_HEADERS
HEADER_NAMES
KLazyLocalizedString
KLocalizedContext
KLocalizedString
KLocalizedTranslator
KLocalization
KuitSetup
REQUIRED_HEADERS KI18n_HEADERS
)
install(FILES
${KI18n_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/ki18n_export.h
DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF}/KI18n" COMPONENT Devel
)
### ktranscript plugin
if (BUILD_WITH_QML)
if (BUILD_SHARED_LIBS)
add_library(ktranscript MODULE)
else()
add_library(ktranscript)
endif()
target_sources(ktranscript PRIVATE
ktranscript.cpp
common_helpers.cpp
)
generate_export_header(ktranscript BASE_NAME KTranscript)
target_link_libraries(ktranscript PRIVATE Qt6::Qml Qt6::Core)
if (BUILD_SHARED_LIBS)
install(TARGETS ktranscript DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6)
else()
target_link_libraries(KF6I18n PRIVATE ktranscript)
install(TARGETS ktranscript EXPORT KF6I18nTargets DESTINATION ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
endif()
endif()
if (BUILD_QCH)
ecm_add_qch(
KF6I18n_QCH
NAME KI18n
BASE_NAME KF6I18n
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KI18n_HEADERS}
"${CMAKE_SOURCE_DIR}/docs/programmers-guide.md"
"${CMAKE_SOURCE_DIR}/docs/translators-guide.md"
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KI18N_EXPORT
KI18N_DEPRECATED_EXPORT
KI18N_DEPRECATED
"KI18N_DEPRECATED_VERSION(x, y, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,2 @@
#! /usr/bin/env bash
$XGETTEXT *.cpp *.h -o $podir/ki18n6.pot
@@ -0,0 +1,96 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <common_helpers_p.h>
// If pos points to alphanumeric X in "...(X)...", which is preceded or
// followed only by non-alphanumerics, then "(X)" gets removed.
static QString removeReducedCJKAccMark(const QString &label, int pos)
{
if (pos > 0 && pos + 1 < label.length() //
&& label[pos - 1] == QLatin1Char('(') //
&& label[pos + 1] == QLatin1Char(')') //
&& label[pos].isLetterOrNumber()) {
// Check if at start or end, ignoring non-alphanumerics.
int len = label.length();
int p1 = pos - 2;
while (p1 >= 0 && !label[p1].isLetterOrNumber()) {
--p1;
}
++p1;
int p2 = pos + 2;
while (p2 < len && !label[p2].isLetterOrNumber()) {
++p2;
}
--p2;
const QStringView labelView(label);
if (p1 == 0) {
return labelView.left(pos - 1) + labelView.mid(p2 + 1);
} else if (p2 + 1 == len) {
return labelView.left(p1) + labelView.mid(pos + 2);
}
}
return label;
}
QString removeAcceleratorMarker(const QString &label_)
{
QString label = label_;
int p = 0;
bool accmarkRemoved = false;
while (true) {
p = label.indexOf(QLatin1Char('&'), p);
if (p < 0 || p + 1 == label.length()) {
break;
}
const QStringView labelView(label);
const QChar marker = label.at(p + 1);
if (marker.isLetterOrNumber()) {
// Valid accelerator.
label = labelView.left(p) + labelView.mid(p + 1);
// May have been an accelerator in CJK-style "(&X)"
// at the start or end of text.
label = removeReducedCJKAccMark(label, p);
accmarkRemoved = true;
} else if (marker == QLatin1Char('&')) {
// Escaped accelerator marker.
label = labelView.left(p) + labelView.mid(p + 1);
}
++p;
}
// If no marker was removed, and there are CJK characters in the label,
// also try to remove reduced CJK marker -- something may have removed
// ampersand beforehand.
if (!accmarkRemoved) {
bool hasCJK = false;
for (const QChar c : std::as_const(label)) {
if (c.unicode() >= 0x2e00) { // rough, but should be sufficient
hasCJK = true;
break;
}
}
if (hasCJK) {
p = 0;
while (true) {
p = label.indexOf(QLatin1Char('('), p);
if (p < 0) {
break;
}
label = removeReducedCJKAccMark(label, p + 1);
++p;
}
}
}
return label;
}
@@ -0,0 +1,33 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef COMMON_HELPERS_P_H
#define COMMON_HELPERS_P_H
#include <QString>
// Standalone (pure Qt) functionality needed internally in more than
// one source file on localization.
/**
* @internal
*
* Removes accelerator marker from a UI text label.
*
* Accelerator marker is not always a plain ampersand (&),
* so it is not enough to just remove it by @c QString::remove().
* The label may contain escaped markers ("&&") which must be resolved
* and skipped, as well as CJK-style markers ("Foo (&F)") where
* the whole parenthesis construct should be removed.
* Therefore always use this function to remove accelerator marker
* from UI labels.
*
* @param label UI label which may contain an accelerator marker
* @return label without the accelerator marker
*/
QString removeAcceleratorMarker(const QString &label);
#endif
@@ -0,0 +1,16 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2016 A. Wilcox <awilfox@adelielinux.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KI18N_CONFIG_H
#define KI18N_CONFIG_H
#cmakedefine01 HAVE_NL_MSG_CAT_CNTR
#define INSTALLED_LOCALE_PREFIX "@IsoCodes_PREFIX@"
#cmakedefine01 HAVE_STATIC_KTRANSCRIPT
#endif
@@ -0,0 +1,187 @@
/* Convenience header for conditional use of GNU <libintl.h>.
SPDX-FileCopyrightText: 1995-1998, 2000-2002, 2004-2006 Free Software Foundation, Inc.
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// clang-format off
#ifndef _LIBGETTEXT_H
#define _LIBGETTEXT_H 1
/* Get declarations of GNU message catalog functions. */
# include <libintl.h>
// libintl.h redefines inline which causes MSVC to abort compilation with the message
// fatal error C1189: #error : The C++ Standard Library forbids macroizing keywords
#undef inline
/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
the gettext() and ngettext() macros. This is an alternative to calling
textdomain(), and is useful for libraries. */
# ifdef DEFAULT_TEXT_DOMAIN
# undef gettext
# define gettext(Msgid) \
dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
# undef ngettext
# define ngettext(Msgid1, Msgid2, N) \
dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
# endif
/* The separator between msgctxt and msgid in a .mo file. */
#define GETTEXT_CONTEXT_GLUE "\004"
/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
short and rarely need to change.
The letter 'p' stands for 'particular' or 'special'. */
#ifdef DEFAULT_TEXT_DOMAIN
# define pgettext(Msgctxt, Msgid) \
pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid)
#else
# define pgettext(Msgctxt, Msgid) \
pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid)
#endif
#define dpgettext(Domainname, Msgctxt, Msgid) \
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
pgettext_aux(const char *domain,
const char *msg_ctxt_id, const char *msgid)
{
const char *translation = dgettext(domain, msg_ctxt_id);
if (translation == msg_ctxt_id) {
return msgid;
} else {
return translation;
}
}
/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID
can be arbitrary expressions. But for string literals these macros are
less efficient than those above. */
#include <string.h>
#ifndef __STRICT_ANSI__
#define __STRICT_ANSI__
#define __STRICT_ANSI_FORCED__
#endif
#define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS \
(__STRICT_ANSI__ - 0 == 0) && (__GNUC__ >= 3 || __GNUG__ >= 2 /* || __STDC_VERSION__ >= 199901L */ )
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
#include <stdlib.h>
#endif
#define pgettext_expr(Msgctxt, Msgid) \
dpgettext_expr (NULL, Msgctxt, Msgid)
#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
dpgettext_expr (Domainname, Msgctxt, Msgid)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
dpgettext_expr(const char *domain,
const char *msgctxt, const char *msgid)
{
size_t msgctxt_len = strlen(msgctxt) + 1;
size_t msgid_len = strlen(msgid) + 1;
const char *translation;
int translation_found;
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
char msg_ctxt_id[msgctxt_len + msgid_len];
#else
char buf[1024];
char *msg_ctxt_id =
(msgctxt_len + msgid_len <= sizeof(buf)
? buf
: (char *) malloc(msgctxt_len + msgid_len));
if (msg_ctxt_id != nullptr)
#endif
{
memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1);
msg_ctxt_id[msgctxt_len - 1] = '\004';
memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len);
translation = dgettext(domain, msg_ctxt_id);
/* Test must occur before msg_ctxt_id freed */
translation_found = translation != msg_ctxt_id;
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
if (msg_ctxt_id != buf) {
free(msg_ctxt_id);
}
#endif
if (translation_found) {
return translation;
}
}
return msgid;
}
#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
dnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N)
#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
dnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N)
#ifdef __GNUC__
__inline
#else
#ifdef __cplusplus
inline
#endif
#endif
static const char *
dnpgettext_expr(const char *domain,
const char *msgctxt, const char *msgid,
const char *msgid_plural, unsigned long int n)
{
size_t msgctxt_len = strlen(msgctxt) + 1;
size_t msgid_len = strlen(msgid) + 1;
const char *translation;
int translation_found;
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
char msg_ctxt_id[msgctxt_len + msgid_len];
#else
char buf[1024];
char *msg_ctxt_id =
(msgctxt_len + msgid_len <= sizeof(buf)
? buf
: (char *) malloc(msgctxt_len + msgid_len));
if (msg_ctxt_id != nullptr)
#endif
{
memcpy(msg_ctxt_id, msgctxt, msgctxt_len - 1);
msg_ctxt_id[msgctxt_len - 1] = '\004';
memcpy(msg_ctxt_id + msgctxt_len, msgid, msgid_len);
translation = dngettext(domain, msg_ctxt_id, msgid_plural, n);
/* Test must occur before msg_ctxt_id freed */
translation_found = !(translation == msg_ctxt_id || translation == msgid_plural);
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
if (msg_ctxt_id != buf) {
free(msg_ctxt_id);
}
#endif
if (translation_found) {
return translation;
}
}
return (n == 1 ? msgid : msgid_plural);
}
#ifdef __STRICT_ANSI_FORCED__
#undef __STRICT_ANSI__
#undef __STRICT_ANSI_FORCED__
#endif
#endif /* _LIBGETTEXT_H */
// clang-format on
@@ -0,0 +1,414 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Hans Petter Bieker <bieker@kde.org>
SPDX-FileCopyrightText: 2012, 2013 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "config.h"
#include <kcatalog_p.h>
#include "ki18n_logging.h"
#include <QByteArray>
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMutexLocker>
#include <QSet>
#include <QStandardPaths>
#include <QStringList>
#ifdef Q_OS_ANDROID
#include <QCoreApplication>
#include <QJniEnvironment>
#include <QJniObject>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
#if __ANDROID_API__ < 23
#include <dlfcn.h>
#endif
#endif
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <locale.h>
#include <stdlib.h>
#include "gettext.h" // Must be included after <stdlib.h>
// not defined on win32 :(
#ifdef _WIN32
#ifndef LC_MESSAGES
#define LC_MESSAGES 42
#endif
#endif
#if HAVE_NL_MSG_CAT_CNTR
extern "C" int Q_DECL_IMPORT _nl_msg_cat_cntr;
#endif
static char *s_langenv = nullptr;
static const int s_langenvMaxlen = 64;
// = "LANGUAGE=" + 54 chars for language code + terminating null \0 character
static void copyToLangArr(const QByteArray &lang)
{
const int bytes = std::snprintf(s_langenv, s_langenvMaxlen, "LANGUAGE=%s", lang.constData());
if (bytes < 0) {
qCWarning(KI18N) << "There was an error while writing LANGUAGE environment variable:" << std::strerror(errno);
} else if (bytes > (s_langenvMaxlen - 1)) { // -1 for the \0 character
qCWarning(KI18N) << "The value of the LANGUAGE environment variable:" << lang << "( size:" << lang.size() << "),\n"
<< "was longer than (and consequently truncated to) the max. length of:" << (s_langenvMaxlen - strlen("LANGUAGE=") - 1);
}
}
class KCatalogStaticData
{
public:
KCatalogStaticData()
{
#ifdef Q_OS_ANDROID
QJniEnvironment env;
QJniObject context = QNativeInterface::QAndroidApplication::context();
m_assets = context.callObjectMethod("getAssets", "()Landroid/content/res/AssetManager;");
m_assetMgr = AAssetManager_fromJava(env.jniEnv(), m_assets.object());
#if __ANDROID_API__ < 23
fmemopenFunc = reinterpret_cast<decltype(fmemopenFunc)>(dlsym(RTLD_DEFAULT, "fmemopen"));
#endif
#endif
}
QHash<QByteArray /*domain*/, QString /*directory*/> customCatalogDirs;
QMutex mutex;
#ifdef Q_OS_ANDROID
QJniObject m_assets;
AAssetManager *m_assetMgr = nullptr;
#if __ANDROID_API__ < 23
FILE *(*fmemopenFunc)(void *, size_t, const char *);
#endif
#endif
};
Q_GLOBAL_STATIC(KCatalogStaticData, catalogStaticData)
class KCatalogPrivate
{
public:
KCatalogPrivate();
QByteArray domain;
QByteArray language;
QByteArray localeDir;
QByteArray systemLanguage;
bool bindDone;
static QByteArray currentLanguage;
void setupGettextEnv();
void resetSystemLanguage();
};
KCatalogPrivate::KCatalogPrivate()
: bindDone(false)
{
}
QByteArray KCatalogPrivate::currentLanguage;
KCatalog::KCatalog(const QByteArray &domain, const QString &language_)
: d(new KCatalogPrivate)
{
d->domain = domain;
d->language = QFile::encodeName(language_);
d->localeDir = QFile::encodeName(catalogLocaleDir(domain, language_));
if (!d->localeDir.isEmpty()) {
// Always get translations in UTF-8, regardless of user's environment.
bind_textdomain_codeset(d->domain, "UTF-8");
// Invalidate current language, to trigger binding at next translate call.
KCatalogPrivate::currentLanguage.clear();
if (!s_langenv) {
// Call putenv only here, to initialize LANGUAGE variable.
// Later only change s_langenv to what is currently needed.
// This doesn't work on Windows though, so there we need putenv calls on every change
s_langenv = new char[s_langenvMaxlen];
copyToLangArr(qgetenv("LANGUAGE"));
putenv(s_langenv);
}
}
}
KCatalog::~KCatalog() = default;
#if defined(Q_OS_ANDROID) && __ANDROID_API__ < 23
static QString androidUnpackCatalog(const QString &relpath)
{
// the catalog files are no longer extracted to the local file system
// by androiddeployqt starting with Qt 5.14, libintl however needs
// local files rather than qrc: or asset: URLs, so we unpack the .mo
// files on demand to the local cache folder
const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/org.kde.ki18n/") + relpath;
QFileInfo cacheFile(cachePath);
if (cacheFile.exists()) {
return cachePath;
}
const QString assetPath = QLatin1String("assets:/share/locale/") + relpath;
if (!QFileInfo::exists(assetPath)) {
return {};
}
QDir().mkpath(cacheFile.absolutePath());
QFile f(assetPath);
if (!f.copy(cachePath)) {
qCWarning(KI18N) << "Failed to copy catalog:" << f.errorString() << assetPath << cachePath;
return {};
}
return cachePath;
}
#endif
QString KCatalog::catalogLocaleDir(const QByteArray &domain, const QString &language)
{
QString relpath = QStringLiteral("%1/LC_MESSAGES/%2.mo").arg(language, QFile::decodeName(domain));
{
QMutexLocker lock(&catalogStaticData->mutex);
const QString customLocaleDir = catalogStaticData->customCatalogDirs.value(domain);
const QString filename = customLocaleDir + QLatin1Char('/') + relpath;
if (!customLocaleDir.isEmpty() && QFileInfo::exists(filename)) {
#if defined(Q_OS_ANDROID)
// The exact file name must be returned on Android because libintl-lite loads a catalog by filename with bindtextdomain()
return filename;
#else
return customLocaleDir;
#endif
}
}
#if defined(Q_OS_ANDROID)
#if __ANDROID_API__ < 23
// fall back to copying the catalog to the file system on old systems
// without fmemopen()
if (!catalogStaticData->fmemopenFunc) {
return androidUnpackCatalog(relpath);
}
#endif
const QString assetPath = QLatin1String("assets:/share/locale/") + relpath;
if (!QFileInfo::exists(assetPath)) {
return {};
}
return assetPath;
#else
QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("locale/") + relpath);
#ifdef Q_OS_WIN
// QStandardPaths fails on Windows for executables that aren't properly deployed yet, such as unit tests
if (file.isEmpty()) {
const QString p = QLatin1String(INSTALLED_LOCALE_PREFIX) + QLatin1String("/bin/data/locale/") + relpath;
if (QFile::exists(p)) {
file = p;
}
}
#endif
QString localeDir;
if (!file.isEmpty()) {
// Path of the locale/ directory must be returned.
localeDir = QFileInfo(file.left(file.size() - relpath.size())).absolutePath();
}
return localeDir;
#endif
}
QSet<QString> KCatalog::availableCatalogLanguages(const QByteArray &domain_)
{
QString domain = QFile::decodeName(domain_);
QStringList localeDirPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory);
#ifdef Q_OS_WIN
// QStandardPaths fails on Windows for executables that aren't properly deployed yet, such as unit tests
localeDirPaths += QLatin1String(INSTALLED_LOCALE_PREFIX) + QLatin1String("/bin/data/locale/");
#endif
{
QMutexLocker lock(&catalogStaticData->mutex);
auto it = catalogStaticData->customCatalogDirs.constFind(domain_);
if (it != catalogStaticData->customCatalogDirs.constEnd()) {
localeDirPaths.prepend(*it);
}
}
QSet<QString> availableLanguages;
for (const QString &localDirPath : std::as_const(localeDirPaths)) {
QDir localeDir(localDirPath);
const QStringList languages = localeDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
for (const QString &language : languages) {
QString relPath = QStringLiteral("%1/LC_MESSAGES/%2.mo").arg(language, domain);
if (localeDir.exists(relPath)) {
availableLanguages.insert(language);
}
}
}
return availableLanguages;
}
#ifdef Q_OS_ANDROID
static void androidAssetBindtextdomain(const QByteArray &domain, const QByteArray &localeDir)
{
AAsset *asset = AAssetManager_open(catalogStaticData->m_assetMgr, localeDir.mid(8).constData(), AASSET_MODE_UNKNOWN);
if (!asset) {
qWarning() << "unable to load asset" << localeDir;
return;
}
off64_t size = AAsset_getLength64(asset);
const void *buffer = AAsset_getBuffer(asset);
#if __ANDROID_API__ >= 23
FILE *moFile = fmemopen(const_cast<void *>(buffer), size, "r");
#else
FILE *moFile = catalogStaticData->fmemopenFunc(const_cast<void *>(buffer), size, "r");
#endif
loadMessageCatalogFile(domain, moFile);
fclose(moFile);
AAsset_close(asset);
}
#endif
void KCatalogPrivate::setupGettextEnv()
{
// Point Gettext to current language, recording system value for recovery.
systemLanguage = qgetenv("LANGUAGE");
if (systemLanguage != language) {
// putenv has been called in the constructor,
// it is enough to change the string set there.
copyToLangArr(language);
#ifdef Q_OS_WINDOWS
putenv(s_langenv);
#endif
}
// Rebind text domain if language actually changed from the last time,
// as locale directories may differ for different languages of same catalog.
if (language != currentLanguage || !bindDone) {
Q_ASSERT_X(QCoreApplication::instance(), "KCatalogPrivate::setupGettextEnv", "You need to instantiate a Q*Application before using KCatalog");
if (!QCoreApplication::instance()) {
qCWarning(KI18N) << "KCatalog being used without a Q*Application instance. Some translations won't work";
}
currentLanguage = language;
bindDone = true;
// qDebug() << "bindtextdomain" << domain << localeDir;
#ifdef Q_OS_ANDROID
if (localeDir.startsWith("assets:/")) {
androidAssetBindtextdomain(domain, localeDir);
} else {
bindtextdomain(domain, localeDir);
}
#else
bindtextdomain(domain, localeDir);
#endif
#if HAVE_NL_MSG_CAT_CNTR
// Magic to make sure GNU Gettext doesn't use stale cached translation
// from previous language.
++_nl_msg_cat_cntr;
#endif
}
}
void KCatalogPrivate::resetSystemLanguage()
{
if (language != systemLanguage) {
copyToLangArr(systemLanguage);
#ifdef Q_OS_WINDOWS
putenv(s_langenv);
#endif
}
}
QString KCatalog::translate(const QByteArray &msgid) const
{
if (!d->localeDir.isEmpty()) {
QMutexLocker locker(&catalogStaticData()->mutex);
d->setupGettextEnv();
const char *msgid_char = msgid.constData();
const char *msgstr = dgettext(d->domain.constData(), msgid_char);
d->resetSystemLanguage();
return msgstr != msgid_char // Yes we want pointer comparison
? QString::fromUtf8(msgstr)
: QString();
} else {
return QString();
}
}
QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid) const
{
if (!d->localeDir.isEmpty()) {
QMutexLocker locker(&catalogStaticData()->mutex);
d->setupGettextEnv();
const char *msgid_char = msgid.constData();
const char *msgstr = dpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid_char);
d->resetSystemLanguage();
return msgstr != msgid_char // Yes we want pointer comparison
? QString::fromUtf8(msgstr)
: QString();
} else {
return QString();
}
}
QString KCatalog::translate(const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const
{
if (!d->localeDir.isEmpty()) {
QMutexLocker locker(&catalogStaticData()->mutex);
d->setupGettextEnv();
const char *msgid_char = msgid.constData();
const char *msgid_plural_char = msgid_plural.constData();
const char *msgstr = dngettext(d->domain.constData(), msgid_char, msgid_plural_char, n);
d->resetSystemLanguage();
// If original and translation are same, dngettext will return
// the original pointer, which is generally fine, except in
// the corner cases where e.g. msgstr[1] is same as msgid.
// Therefore check for pointer difference only with msgid or
// only with msgid_plural, and not with both.
return (n == 1 && msgstr != msgid_char) || (n != 1 && msgstr != msgid_plural_char) ? QString::fromUtf8(msgstr) : QString();
} else {
return QString();
}
}
QString KCatalog::translate(const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const
{
if (!d->localeDir.isEmpty()) {
QMutexLocker locker(&catalogStaticData()->mutex);
d->setupGettextEnv();
const char *msgid_char = msgid.constData();
const char *msgid_plural_char = msgid_plural.constData();
const char *msgstr = dnpgettext_expr(d->domain.constData(), msgctxt.constData(), msgid_char, msgid_plural_char, n);
d->resetSystemLanguage();
return (n == 1 && msgstr != msgid_char) || (n != 1 && msgstr != msgid_plural_char) ? QString::fromUtf8(msgstr) : QString();
} else {
return QString();
}
}
void KCatalog::addDomainLocaleDir(const QByteArray &domain, const QString &path)
{
QMutexLocker locker(&catalogStaticData()->mutex);
catalogStaticData()->customCatalogDirs.insert(domain, path);
}
@@ -0,0 +1,121 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 Hans Petter Bieker <bieker@kde.org>
SPDX-FileCopyrightText: 2012, 2013 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCATALOG_H
#define KCATALOG_H
#include "ki18n_export.h"
#include <QByteArray>
#include <QSet>
#include <QString>
#include <memory>
class KCatalogPrivate;
/**
* This class abstracts a Gettext message catalog.
* It takes care of needed Gettext bindings.
*
* @see KLocalizedString
* @internal exported only for use in KI18nLocaleData.
*/
class KI18N_EXPORT KCatalog
{
public:
/**
* Constructor.
*
* @param name translation domain
* @param language translation language
*/
KCatalog(const QByteArray &domain, const QString &language);
/**
* Destructor.
*/
~KCatalog();
/**
* Get translation of the given message text.
*
* Do not pass empty message text.
*
* @param msgid message text
*
* @return translated message if found, <tt>QString()</tt> otherwise
*/
QString translate(const QByteArray &msgid) const;
/**
* Get translation of the given message text with given context.
*
* Do not pass empty message text.
*
* @param msgctxt message context
* @param msgid message text
*
* @return translated message if found, <tt>QString()</tt> otherwise
*/
QString translate(const QByteArray &msgctxt, const QByteArray &msgid) const;
/**
* Get translation of given message with plural forms.
*
* Do not pass empty message text.
*
* @param msgid singular message text
* @param msgid_plural plural message text
* @param n number for which the plural form is needed
*
* @return translated message if found, <tt>QString()</tt> otherwise
*/
QString translate(const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const;
/**
* Get translation of given message with plural forms with given context.
*
* Do not pass empty message text.
*
* @param msgctxt message context
* @param msgid singular message text
* @param msgid_plural plural message text
* @param n number for which the plural form is needed
*
* @return translated message if found, <tt>QString()</tt> otherwise
*/
QString translate(const QByteArray &msgctxt, const QByteArray &msgid, const QByteArray &msgid_plural, qulonglong n) const;
/**
* Find the locale directory for the given domain in the given language.
*
* @param domain translation domain
* @param language language of the catalog
*
* @return the locale directory if found, <tt>QByteArray()</tt> otherwise.
*/
static QString catalogLocaleDir(const QByteArray &domain, const QString &language);
/**
* Find the all languages for which the translation catalog
* of given domain exists.
*
* @param domain translation domain
*
* @return set of language codes
*/
static QSet<QString> availableCatalogLanguages(const QByteArray &domain);
static void addDomainLocaleDir(const QByteArray &domain, const QString &path);
private:
Q_DISABLE_COPY(KCatalog)
std::unique_ptr<KCatalogPrivate> const d;
};
#endif
@@ -0,0 +1,555 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLAZYLOCALIZEDSTRING_H
#define KLAZYLOCALIZEDSTRING_H
#include "klocalizedstring.h"
#include <cstddef>
/**
* @class KLazyLocalizedString klazylocalizedstring.h <KLazyLocalizedString>
*
* Lazy-initialized variant of KLocalizedString.
*
* This is a safer replacement for the I18N_NOOP set of macros and allows
* marking strings for extraction without runtime-initializing KLocalizedString instances,
* for storing in static data tables.
*
* Instances of KLazyLocalizedString are not created directly, unless they should be empty.
* Instead they have to be obtained via the kli18n* functions (similar to KLocalizedString
* and the ki18n* functions).
*
* Example usage in a static message table:
* @code
* struct {
* MyClass::VehicleType type;
* KLazyLocalizedString msg;
* } static constexpr const vehicle_msg_table[] = {
* { MyClass::Train, kli18np("%1 train", "%1 trains") },
* { MyClass::Bus, kli18ncp("the vehicle, not the USB one", "%1 bus", "%1 buses") },
* ...
* };
*
* ...
*
* const auto it = std::find_if(std::begin(vehicle_msg_table), std::end(vehicle_msg_table), [vehicleType](const auto &m) { return m.type == vehicleType; });
* QString translatedMessage = (*it).msg.subs(vehicleCount).toString();
* @endcode
*
* @note KLazyLocalizedString is primarily meant for storage in e.g. message tables,
* not for use in API, especially not across translation domains. This is due to
* it not carrying the translation domain in which it was created, but using the
* active translation domain at the point of converting to a KLocalizedString.
*
* @since 5.89
*/
class KLazyLocalizedString
{
public:
/**
* Construct an empty message.
*
* Direct construction is used when e.g. a KLazyLocalizedString field in
* a data table needs to be set, but there is not message to be used.
* Directly constructed instances are not valid for
* finalization by \c toString methods.
*
* \see isEmpty
*/
constexpr inline KLazyLocalizedString() = default;
/** Convert to a KLocalizedString to actually perform the translation and obtain a concrete
* localized string.
*
* The translation domain active at this point will be used. This means this has to be
* called in the same translation domain the corresponding @c kli18n call is in.
*/
Q_REQUIRED_RESULT inline operator KLocalizedString() const
{
if (!m_text) {
return KLocalizedString();
}
#ifdef TRANSLATION_DOMAIN
return KLocalizedString(TRANSLATION_DOMAIN, m_context, m_text, m_plural, m_markupAware);
#else
return KLocalizedString(nullptr, m_context, m_text, m_plural, m_markupAware);
#endif
}
/**
* Check whether the message is empty.
*
* The message is considered empty if the object was constructed
* via the default constructor.
*
* Empty messages are not valid for finalization.
* The behavior of calling toString() on them is undefined.
* In debug mode, an error mark may appear in the returned string.
*
* \return \c true if the message is empty, \c false otherwise
*/
Q_REQUIRED_RESULT constexpr inline bool isEmpty() const
{
return (m_text == nullptr) || (m_text[0] == '\0');
}
/** Returns the raw untranslated text as passed to @p kli18n*. */
Q_REQUIRED_RESULT constexpr inline const char *untranslatedText() const
{
return m_text;
}
/**
* Finalize the translation.
*
* Creates translated QString. If the string has placeholders,
* make sure to call instead \c KLazyLocalizedString::subs and
* further \c KLocalizedString::subs methods before finalizing.
* Translated text is searched for based on the global locale.
*
* \return finalized translation
*/
Q_REQUIRED_RESULT inline QString toString() const
{
return this->operator KLocalizedString().toString();
}
/**
* Like toString(), but look for translation only in given languages.
*
* Given languages override languages defined by the global locale.
* If \p languages is empty, original message is returned.
*
* \param languages list of language codes (by decreasing priority)
* \return finalized translation
*/
Q_REQUIRED_RESULT inline QString toString(const QStringList &languages) const
{
return this->operator KLocalizedString().toString(languages);
}
/**
* Like toString(), but look for translation in the given domain.
*
* \param domain the translation domain
* \return finalized translation
*/
Q_REQUIRED_RESULT inline QString toString(const char *domain) const
{
return this->operator KLocalizedString().toString(domain);
}
/**
* Like toString(), but resolve KUIT markup into given visual format.
*
* Given visual format overrides that implied by the context UI marker.
* If the message is not markup-aware, this is same as toString().
*
* \param format the target visual format
* \return finalized translation
*/
Q_REQUIRED_RESULT inline QString toString(Kuit::VisualFormat format) const
{
return this->operator KLocalizedString().toString(format);
}
/**
* Indicate to look for translation only in given languages.
*
* \param languages list of language codes (by decreasing priority)
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString withLanguages(const QStringList &languages) const
{
return this->operator KLocalizedString().withLanguages(languages);
}
/**
* Indicate to look for translation in the given domain.
*
* \param domain the translation domain
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString withDomain(const char *domain) const
{
return this->operator KLocalizedString().withDomain(domain);
}
/**
* Indicate to resolve KUIT markup into given visual format.
*
* If the message is not markup-aware, this has no effect.
*
* \param format the target visual format
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString withFormat(Kuit::VisualFormat format) const
{
return this->operator KLocalizedString().withFormat(format);
}
/**
* Substitute an int argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute an unsigned int argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(uint a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute a long argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(long a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute an unsigned long argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(ulong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute a long long argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(qlonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute an unsigned long long argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param base the radix used to represent the number as a string.
* Valid values range from 2 to 36
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(qulonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, base, fillChar);
}
/**
* Substitute a double argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param format type of floating point formatting, like in QString::arg
* \param precision number of digits after the decimal separator
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, format, precision, fillChar);
}
/**
* Substitute a \c QChar argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(QChar a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
}
/**
* Substitute a \c QString argument into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
}
/**
* Substitute another KLocalizedString into the message.
*
* \param a the argument
* \param fieldWidth width of the formatted field, padded by spaces.
* Positive value aligns right, negative aligns left
* \param fillChar the character used to fill up the empty places when
* field width is greater than argument width
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString subs(const KLocalizedString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
{
return this->operator KLocalizedString().subs(a, fieldWidth, fillChar);
}
/**
* Add dynamic context to the message.
*
* See \ref dyn_ctxt for use cases.
*
* \param key context key
* \param value context value
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString inContext(const QString &key, const QString &value) const
{
return this->operator KLocalizedString().inContext(key, value);
}
/**
* Relax matching between placeholders and arguments.
*
* Normally the placeholders should start from %1 and have no gaps,
* and on finalization there must be exactly as many arguments
* supplied through \c subs methods as there are unique plaecholders.
* If this is not satisfied, in debug mode warnings are printed
* and the finalized string may contain error marks.
*
* This method relaxes the placeholder-argument matching,
* such that there must only be an argument available for
* every present unique placeholder (taking placeholder numbers
* to be 1-based indices into the argument list).
* This can come useful in some situations.
*
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString relaxSubs() const
{
return this->operator KLocalizedString().relaxSubs();
}
/**
* Do not resolve KUIT markup.
*
* If the message is markup-aware
* (constructed by one of \c \*xi18n\* calls),
* this function can be used to make it non-markup-aware.
* This may be useful for debugging markup.
*
* \return updated KLocalizedString
*/
Q_REQUIRED_RESULT inline KLocalizedString ignoreMarkup() const
{
return this->operator KLocalizedString().ignoreMarkup();
}
private:
template<std::size_t TextSize>
friend inline constexpr KLazyLocalizedString kli18n(const char (&text)[TextSize]);
template<std::size_t ContextSize, std::size_t TextSize>
friend constexpr inline KLazyLocalizedString kli18nc(const char (&context)[ContextSize], const char (&text)[TextSize]);
template<std::size_t SingularSize, std::size_t PluralSize>
friend constexpr inline KLazyLocalizedString kli18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
friend constexpr inline KLazyLocalizedString
kli18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
template<std::size_t TextSize>
friend constexpr inline KLazyLocalizedString klxi18n(const char (&text)[TextSize]);
template<std::size_t ContextSize, std::size_t TextSize>
friend constexpr inline KLazyLocalizedString klxi18nc(const char (&context)[ContextSize], const char (&text)[TextSize]);
template<std::size_t SingularSize, std::size_t PluralSize>
friend constexpr inline KLazyLocalizedString klxi18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
friend constexpr inline KLazyLocalizedString
klxi18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize]);
constexpr inline KLazyLocalizedString(const char *context, const char *text, const char *plural, bool markupAware)
: m_context(context)
, m_text(text)
, m_plural(plural)
, m_markupAware(markupAware)
{
}
const char *m_context = nullptr;
const char *m_text = nullptr;
const char *m_plural = nullptr;
bool m_markupAware = false;
};
/**
* Mark the string @p text for extraction.
*
* \param text string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t TextSize>
constexpr inline KLazyLocalizedString kli18n(const char (&text)[TextSize])
{
return KLazyLocalizedString(nullptr, text, nullptr, false);
}
/**
* Mark the string @p text with @p context for extraction.
*
* \param context context of the string
* \param text string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t ContextSize, std::size_t TextSize>
constexpr inline KLazyLocalizedString kli18nc(const char (&context)[ContextSize], const char (&text)[TextSize])
{
return KLazyLocalizedString(context, text, nullptr, false);
}
/**
* Mark the string @p singular and @p plural for extraction.
*
* \param singular singular form of the string to translate
* \param plural plural form of the string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t SingularSize, std::size_t PluralSize>
constexpr inline KLazyLocalizedString kli18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize])
{
return KLazyLocalizedString(nullptr, singular, plural, false);
}
/**
* Mark the string @p singular and @p plural with @p context for extraction.
*
* \param context context of the string
* \param singular singular form of the string to translate
* \param plural plural form of the string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
constexpr inline KLazyLocalizedString kli18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize])
{
return KLazyLocalizedString(context, singular, plural, false);
}
/**
* Mark the markup-aware string @p text for extraction.
*
* \param text string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t TextSize>
constexpr inline KLazyLocalizedString klxi18n(const char (&text)[TextSize])
{
return KLazyLocalizedString(nullptr, text, nullptr, true);
}
/**
* Mark the markup-aware string @p text with @p context for extraction.
*
* \param context context of the string
* \param text string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t ContextSize, std::size_t TextSize>
constexpr inline KLazyLocalizedString klxi18nc(const char (&context)[ContextSize], const char (&text)[TextSize])
{
return KLazyLocalizedString(context, text, nullptr, true);
}
/**
* Mark the markup-aware string @p singular and @p plural for extraction.
*
* \param singular singular form of the string to translate
* \param plural plural form of the string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t SingularSize, std::size_t PluralSize>
constexpr inline KLazyLocalizedString klxi18np(const char (&singular)[SingularSize], const char (&plural)[PluralSize])
{
return KLazyLocalizedString(nullptr, singular, plural, true);
}
/**
* Mark the markup-aware string @p singular and @p plural with @p context for extraction.
*
* \param context context of the string
* \param singular singular form of the string to translate
* \param plural plural form of the string to translate
* \return KLazyLocalizedString for deferred translation.
* \since 5.89
*/
template<std::size_t ContextSize, std::size_t SingularSize, std::size_t PluralSize>
constexpr inline KLazyLocalizedString klxi18ncp(const char (&context)[ContextSize], const char (&singular)[SingularSize], const char (&plural)[PluralSize])
{
return KLazyLocalizedString(context, singular, plural, true);
}
#endif // KLAZYLOCALIZEDSTRING_H
@@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2024 Lukas Sommer <sommerluk@gmail.com>
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "klocalization.h"
// this file only exists so we check klocalization.h compiles
@@ -0,0 +1,161 @@
/*
SPDX-FileCopyrightText: 2024 Lukas Sommer <sommerluk@gmail.com>
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLOCALIZATION_H
#define KLOCALIZATION_H
#include "klocalizedstring.h"
#include <QObject>
#include <QVariant>
#include <type_traits>
class QDoubleSpinBox;
class QSpinBox;
/**
* @namespace KLocalization
* @brief Namespace containing helpers for localization.
* @since 6.5
*/
namespace KLocalization
{
///@cond hidden
namespace Private
{
constexpr inline const char SpinBoxFormatStringProperty[] = "__KLocalizationFormatStringPrivate";
}
///@endcond
/**
* @brief Retranslates a previously set up format string to the current
* language and updates the spin box.
*
* The format string is initially set up by setupSpinBoxFormatString().
* This function updates the prefix and suffix of a spin box to reflect the
* current language settings. It is useful for responding to language changes,
* such as those triggered by QEvent::LanguageChange.
*
* @tparam T The type of the spin box, which must be either QSpinBox or
* QDoubleSpinBox.
* @param spinBox Pointer to the spin box.
*
* @post The prefix and suffix of the spin box are updated to reflect the
* current language.
*
* @sa @ref setupSpinBoxFormatString
*
* @since 6.5
*/
template<typename T>
inline void retranslateSpinBoxFormatString(T *spinBox)
{
constexpr bool isSpinBox = std::is_base_of_v<QSpinBox, T> || std::is_base_of_v<QDoubleSpinBox, T>;
static_assert(isSpinBox, "First argument must be a QSpinBox or QDoubleSpinBox.");
const auto lString = spinBox->property(Private::SpinBoxFormatStringProperty).template value<KLocalizedString>();
// The KLocalizedString::subs() method performs two tasks:
// 1. It replaces placeholders (%1, %2, ...) in the string with actual
// content.
// 2. If the argument is an integer, it selects the appropriate plural form
// based on the value.
// In this context, the string is expected not to contain any standard
// placeholders (%1, %2, ...). Instead, it should contain a custom
// placeholder (%v) which is ignored by KLocalizedString::subs().
// The only purpose of calling KLocalizedString::subs() here is to ensure
// the correct plural form is used when spinBox->value() is an integer.
// If spinBox->value() is a double, KLocalizedString::subs() does not
// perform any operations on the string since plural handling applies only
// to integer values.
const auto translation = lString.subs(spinBox->value()).toString();
const auto parts = translation.split(QLatin1StringView("%v"));
if (parts.count() == 2) {
spinBox->setPrefix(parts.at(0));
spinBox->setSuffix(parts.at(1));
} else {
spinBox->setPrefix(QString());
spinBox->setSuffix(QString());
}
}
/**
* @brief Sets up a format string for internationalizing spin boxes.
*
* This function allows the customization of prefix and suffix for spin boxes
* (QSpinBox and QDoubleSpinBox), considering internationalization needs.
*
* Spin boxes display a number and possibly a prefix and/or suffix. However,
* in some languages, the position of the prefix and suffix may be reversed
* compared to English. Example: In English, you write 50%, but in Turkish,
* you write %50. Qt does not offer an out-of-the-box solution for this. This
* helper now provides complete internationalization for prefixes and suffixes
* of spin boxes.
*
* For QSpinBox it also provides correct plural handling by installing a
* handler for the valueChanged() signal to update prefix and suffix whenever
* the spin box value changes in the future.
*
* Example usage:
* @code
* QDoubleSpinBox doubleBox;
* KLocalization::setupSpinBoxFormatString(
* &doubleBox,
* ki18nc("@item %v is a number and the second % is the percent sign", "%v%"));
* // Turkish translation: "%%v"
*
* QSpinBox intBox;
* KLocalization::setupSpinBoxFormatString(
* &intBox,
* ki18ncp("@item %v is a number", "Baking %v cake", "Baking %v cakes"));
* @endcode
*
* @tparam T The type of the spin box, which must be either QSpinBox or QDoubleSpinBox.
* @param spinBox Pointer to the spin box.
* @param formatString A localized string in the format "PREFIX%vSUFFIX".
* - For QDoubleSpinBox, plural forms in @p formatString are ignored
* and should be avoided. Use @ref KLocalizedString::ki18nc "ki18nc()"
* to generate the format string.
* - For QSpinBox, if @p formatString includes plural forms, they are
* utilized. While optional, their use is highly recommended for
* accurate pluralization. Use @ref KLocalizedString::ki18ncp "ki18ncp()"
* to generate the format string.
*
* @note It is safe to call this function multiple times on the same spin box.
*
* @sa @ref retranslateSpinBoxFormatString
*
* @since 6.5
*/
template<typename T>
inline void setupSpinBoxFormatString(T *spinBox, const KLocalizedString &formatString)
{
constexpr bool isSpinBox = std::is_base_of_v<QSpinBox, T>;
constexpr bool isDoubleSpinBox = std::is_base_of_v<QDoubleSpinBox, T>;
static_assert(isSpinBox || isDoubleSpinBox, "First argument must be a QSpinBox or QDoubleSpinBox.");
if constexpr (isSpinBox) {
const bool hasSetup = !spinBox->property(Private::SpinBoxFormatStringProperty).isNull();
if (!hasSetup) {
QObject::connect(spinBox, &T::valueChanged, spinBox, [spinBox]() {
retranslateSpinBoxFormatString(spinBox);
});
}
}
// Using relaxSubs() to avoid error marks if the library user did pass
// a singular-only KLocalizedString.
spinBox->setProperty(Private::SpinBoxFormatStringProperty, QVariant::fromValue(formatString.relaxSubs()));
retranslateSpinBoxFormatString(spinBox);
}
}
#endif
@@ -0,0 +1,584 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// Undefine this because we don't want our i18n*() method names to be turned into i18nd*()
#undef TRANSLATION_DOMAIN
#include "klocalizedcontext.h"
#if KI18N_BUILD_DEPRECATED_SINCE(6, 8)
#include <klocalizedstring.h>
#include "ki18n_logging.h"
class KLocalizedContextPrivate
{
public:
QString m_translationDomain;
};
KLocalizedContext::KLocalizedContext(QObject *parent)
: QObject(parent)
, d(new KLocalizedContextPrivate)
{
}
KLocalizedContext::~KLocalizedContext()
{
delete d;
}
QString KLocalizedContext::translationDomain() const
{
return d->m_translationDomain;
}
void KLocalizedContext::setTranslationDomain(const QString &domain)
{
if (domain != d->m_translationDomain) {
d->m_translationDomain = domain;
Q_EMIT translationDomainChanged(domain);
}
}
static void subsVariant(KLocalizedString &trMessage, const QVariant &value)
{
switch (value.userType()) {
case QMetaType::QString:
trMessage = trMessage.subs(value.toString());
break;
case QMetaType::Int:
trMessage = trMessage.subs(value.toInt());
break;
case QMetaType::Double:
trMessage = trMessage.subs(value.toDouble());
break;
case QMetaType::Char:
trMessage = trMessage.subs(value.toChar());
break;
default:
if (value.canConvert<QString>()) {
trMessage = trMessage.subs(value.toString());
} else {
trMessage = trMessage.subs(QStringLiteral("???"));
qCWarning(KI18N) << "couldn't convert" << value << "to translate";
}
}
}
static void resolveMessage(KLocalizedString &trMessage,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10 = QVariant())
{
if (param1.isValid()) {
subsVariant(trMessage, param1);
}
if (param2.isValid()) {
subsVariant(trMessage, param2);
}
if (param3.isValid()) {
subsVariant(trMessage, param3);
}
if (param4.isValid()) {
subsVariant(trMessage, param4);
}
if (param5.isValid()) {
subsVariant(trMessage, param5);
}
if (param6.isValid()) {
subsVariant(trMessage, param6);
}
if (param7.isValid()) {
subsVariant(trMessage, param7);
}
if (param8.isValid()) {
subsVariant(trMessage, param8);
}
if (param9.isValid()) {
subsVariant(trMessage, param9);
}
if (param10.isValid()) {
subsVariant(trMessage, param10);
}
}
static void resolvePlural(KLocalizedString &trMessage, const QVariant &param)
{
trMessage = trMessage.subs(param.toInt());
}
QString KLocalizedContext::i18n(const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (message.isEmpty()) {
qCWarning(KI18N) << "i18n() needs at least one parameter";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = ki18n(message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18nc(const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "i18nc() needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = ki18nc(context.toUtf8().constData(), message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18np(const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18np() needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = ki18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = ki18np(singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ncp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage =
ki18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = ki18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18nd(const QString &domain,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "i18nd() needs at least two parameters";
return QString();
}
KLocalizedString trMessage = ki18nd(domain.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "i18ndc() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = ki18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ndp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = ki18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::i18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "i18ndcp() needs at least four arguments";
return QString();
}
KLocalizedString trMessage =
ki18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
/////////////////////////
QString KLocalizedContext::xi18n(const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (message.isEmpty()) {
qCWarning(KI18N) << "xi18n() needs at least one parameter";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18nd(d->m_translationDomain.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = kxi18n(message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18nc(const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "xi18nc() needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18ndc(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
} else {
trMessage = kxi18nc(context.toUtf8().constData(), message.toUtf8().constData());
}
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18np(const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18np() needs at least two arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage = kxi18ndp(d->m_translationDomain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = kxi18np(singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ncp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage;
if (!d->m_translationDomain.isEmpty()) {
trMessage =
kxi18ndcp(d->m_translationDomain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
} else {
trMessage = kxi18ncp(context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
}
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18nd(const QString &domain,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "xi18nd() needs at least two parameters";
return QString();
}
KLocalizedString trMessage = kxi18nd(domain.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || message.isEmpty()) {
qCWarning(KI18N) << "x18ndc() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = kxi18ndc(domain.toUtf8().constData(), context.toUtf8().constData(), message.toUtf8().constData());
resolveMessage(trMessage, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ndp() needs at least three arguments";
return QString();
}
KLocalizedString trMessage = kxi18ndp(domain.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
QString KLocalizedContext::xi18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1,
const QVariant &param2,
const QVariant &param3,
const QVariant &param4,
const QVariant &param5,
const QVariant &param6,
const QVariant &param7,
const QVariant &param8,
const QVariant &param9,
const QVariant &param10) const
{
if (domain.isEmpty() || context.isEmpty() || singular.isEmpty() || plural.isEmpty()) {
qCWarning(KI18N) << "xi18ndcp() needs at least four arguments";
return QString();
}
KLocalizedString trMessage =
kxi18ndcp(domain.toUtf8().constData(), context.toUtf8().constData(), singular.toUtf8().constData(), plural.toUtf8().constData());
resolvePlural(trMessage, param1);
resolveMessage(trMessage, param2, param3, param4, param5, param6, param7, param8, param9, param10);
return trMessage.toString();
}
#include "moc_klocalizedcontext.cpp"
#endif
@@ -0,0 +1,287 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLOCALIZEDCONTEXT_H
#define KLOCALIZEDCONTEXT_H
#include <ki18n_export.h>
#if KI18N_ENABLE_DEPRECATED_SINCE(6, 8)
#include <QObject>
#include <QVariant>
/**
* @class KLocalizedContext klocalizedcontext.h <KLocalizedContext>
*
* This class is meant to be used to simplify integration of the KI18n framework
* in QML.
*
* The way to do so, is by creating this object and setting it as a context
* object:
*
* @code
* QQuickView* view = new QQuickView;
* view.engine()->rootContext()->setContextObject(new KLocalizedContext(view));
* @endcode
*
* Then i18n*() and xi18n*() functions should be available for use from the code
* loaded in the engine, for the view.
*
* @note Plural functions differ from the C/C++ version. On QML/JS we can get a
* real value easily. To solve warnings on those cases we'll cast the first argument
* to make sure it's taken into account for the plural.
*
* @since 5.17
* @deprecated since 6.8 Use KLocalizedQmlContext or KLocalization::setupLocalizedContext
* instead.
*/
KI18N_DEPRECATED_VERSION(6, 8, "use KLocalizedQmlContext or KLocalization::setupLocalizedContext() from KF6::I18nQml instead")
class KI18N_EXPORT KLocalizedContext : public QObject
{
Q_OBJECT
/**
* This property only needs to be specified if the context is being run on a library.
* in an application there is no need to set the translation domain as the application's
* domain can be used.
*/
Q_PROPERTY(QString translationDomain READ translationDomain WRITE setTranslationDomain NOTIFY translationDomainChanged)
public:
explicit KLocalizedContext(QObject *parent = nullptr);
~KLocalizedContext() override;
QString translationDomain() const;
void setTranslationDomain(const QString &domain);
Q_INVOKABLE QString i18n(const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18nc(const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18np(const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18nd(const QString &domain,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString i18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18n(const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18nc(const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18np(const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ncp(const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18nd(const QString &domain,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndc(const QString &domain,
const QString &context,
const QString &message,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndp(const QString &domain,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_INVOKABLE QString xi18ndcp(const QString &domain,
const QString &context,
const QString &singular,
const QString &plural,
const QVariant &param1 = QVariant(),
const QVariant &param2 = QVariant(),
const QVariant &param3 = QVariant(),
const QVariant &param4 = QVariant(),
const QVariant &param5 = QVariant(),
const QVariant &param6 = QVariant(),
const QVariant &param7 = QVariant(),
const QVariant &param8 = QVariant(),
const QVariant &param9 = QVariant(),
const QVariant &param10 = QVariant()) const;
Q_SIGNALS:
void translationDomainChanged(const QString &translationDomain);
private:
// intentionally not a unique_ptr as this file gets included a lot and using a unique_ptr
// results in too many template instantiations
class KLocalizedContextPrivate *const d;
};
#endif
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "klocalizedtranslator.h"
#include "klocalizedstring.h"
// Qt
#include <QMetaObject>
#include <QMetaProperty>
class KLocalizedTranslatorPrivate
{
public:
QString translationDomain;
QSet<QString> monitoredContexts;
};
KLocalizedTranslator::KLocalizedTranslator(QObject *parent)
: QTranslator(parent)
, d(new KLocalizedTranslatorPrivate)
{
}
KLocalizedTranslator::~KLocalizedTranslator()
{
}
void KLocalizedTranslator::setTranslationDomain(const QString &translationDomain)
{
d->translationDomain = translationDomain;
}
void KLocalizedTranslator::addContextToMonitor(const QString &context)
{
d->monitoredContexts.insert(context);
}
void KLocalizedTranslator::removeContextToMonitor(const QString &context)
{
d->monitoredContexts.remove(context);
}
QString KLocalizedTranslator::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const
{
if (d->translationDomain.isEmpty() || !d->monitoredContexts.contains(QString::fromUtf8(context))) {
return QTranslator::translate(context, sourceText, disambiguation, n);
}
if (qstrlen(disambiguation) == 0) {
return ki18nd(d->translationDomain.toUtf8().constData(), sourceText).toString();
} else {
return ki18ndc(d->translationDomain.toUtf8().constData(), disambiguation, sourceText).toString();
}
}
#include "moc_klocalizedtranslator.cpp"
@@ -0,0 +1,98 @@
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KLOCALIZEDTRANSLATOR_H
#define KLOCALIZEDTRANSLATOR_H
#include <ki18n_export.h>
#include <QTranslator>
#include <memory>
class KLocalizedTranslatorPrivate;
/**
* @class KLocalizedTranslator klocalizedtranslator.h <KLocalizedTranslator>
*
* @brief A QTranslator using KLocalizedString for translations.
*
* This class allows to translate strings in Qt's translation system with KLocalizedString.
* An example is the translation of a dynamically loaded user interface through QUILoader.
*
* To use this Translator install it in the QCoreApplication and provide the translation domain
* to be used. The Translator can operate for multiple contexts, those needs to be specified.
*
* Example for translating a UI loaded through QUILoader:
* @code
* // create translator and install in QCoreApplication
* KLocalizedTranslator *translator = new KLocalizedTranslator(this);
* QCoreApplication::instance()->installTranslator(translator);
* translator->setTranslationDomain(QStringLiteral("MyAppsDomain"));
*
* // create the QUILoader
* QUiLoader *loader = new QUiLoader(this);
* loader->setLanguageChangeEnabled(true);
*
* // load the UI
* QFile uiFile(QStringLiteral("/path/to/userInterface.ui"));
* uiFile.open(QFile::ReadOnly);
* QWidget *loadedWidget = loader->load(&uiFile, this);
* uiFile.close();
*
* // the object name of the loaded UI is the context in this case
* translator->addContextToMonitor(loadedWidget->objectName());
*
* // send a LanguageChange event, this will re-translate using our translator
* QEvent le(QEvent::LanguageChange);
* QCoreApplication::sendEvent(loadedWidget, &le);
* @endcode
*
* @since 5.0
**/
class KI18N_EXPORT KLocalizedTranslator : public QTranslator
{
Q_OBJECT
public:
explicit KLocalizedTranslator(QObject *parent = nullptr);
virtual ~KLocalizedTranslator();
QString translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1) const override;
/**
* Sets the @p translationDomain to be used.
*
* The translation domain is required. Without the translation domain any invocation of
* translate() will be delegated to the base class.
*
* @param translationDomain The translation domain to be used.
**/
void setTranslationDomain(const QString &translationDomain);
/**
* Adds a @p context for which this Translator should be active.
*
* The Translator only translates texts with a context matching one of the monitored contexts.
* If the context is not monitored, the translate() method delegates to the base class.
*
* @param context The context for which the Translator should be active
*
* @see removeContextToMonitor
**/
void addContextToMonitor(const QString &context);
/**
* Stop translating for the given @p context.
*
* @param context The context for which the Translator should no longer be active
*
* @see addContextToMonitor
**/
void removeContextToMonitor(const QString &context);
private:
std::unique_ptr<KLocalizedTranslatorPrivate> const d;
};
#endif // KLOCALIZEDTRANSLATOR_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,83 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTRANSCRIPT_P_H
#define KTRANSCRIPT_P_H
#include <QHash>
#include <QList>
#include <QStringList>
#include <QVariant>
/**
* @internal
* (used by KLocalizedString)
*
* @c KTranscript provides support for programmable translations.
* The class is abstract in order to facilitate dynamic loading.
*
* @author Chusslove Illich <caslav.ilic@gmx.net>
* @short class for supporting programmable translations
*/
class KTranscript
{
public:
/**
* Evaluates interpolation.
*
* @param argv list of interpolation tokens
* @param lang language of the translation
* @param ctry locale country
* @param msgctxt message context
* @param dynctxt dynamic context
* @param msgid original message
* @param subs substitutions for message placeholders
* @param vals values that were formatted to substitutions
* @param ftrans finalized ordinary translation
* @param mods scripting modules to load; the list is cleared after loading
* @param error set to the message detailing the problem, if the script
failed; set to empty otherwise
* @param fallback set to true if the script requested fallback to ordinary
translation; set to false otherwise
* @return resolved interpolation if evaluation succeeded,
* empty string otherwise
*/
virtual QString eval(const QList<QVariant> &argv,
const QString &lang,
const QString &ctry,
const QString &msgctxt,
const QHash<QString, QString> &dynctxt,
const QString &msgid,
const QStringList &subs,
const QList<QVariant> &vals,
const QString &ftrans,
QList<QStringList> &mods,
QString &error,
bool &fallback) = 0;
/**
* Returns the list of calls to execute an all messages after the
* interpolations are done, as evaluations with no parameters.
*
* @param lang language of the translation
* @return list of post calls
*/
virtual QStringList postCalls(const QString &lang) = 0;
/**
* Destructor.
*/
virtual ~KTranscript()
{
}
};
#ifdef KTRANSCRIPT_TESTBUILD
KTranscript *autotestCreateKTranscriptImp();
void autotestDestroyKTranscriptImp();
#endif
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,186 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KUITSETUP_H
#define KUITSETUP_H
#include <ki18n_export.h>
#include <QHash>
#include <QString>
#include <QStringList>
class KuitSetup;
/**
* Global constants and functions related to KUIT markup.
*/
namespace Kuit
{
/**
* Visual formats into which KUIT markup can be resolved.
*/
enum VisualFormat {
/**
* Visual format not defined.
* This value can be explicitly set
* (e.g. through \c KLocalizedString::withFormat)
* to indicate that the format should be decided
* by another mechanism (e.g. context UI marker).
*/
UndefinedFormat = 0,
/**
* Plain text.
*/
PlainText = 10,
/**
* Qt rich text (HTML subset).
*/
RichText = 20,
/**
* Terminal escape sequences.
*/
TermText = 30,
};
/**
* Classification of KUIT tags.
*/
enum TagClass {
/**
* Tags wrapping text inserted into running text.
*/
PhraseTag = 0,
/**
* Tags splitting text into paragraph-level blocks.
*/
StructTag = 1,
};
/**
* Functions accepted by tag formatting functions.
*
* \param languages the target languages (by decreasing priority)
* \param tagName the wrapping tag name
* \param attributes the attribute name-value pairs in the tag
* \param text the wrapped text
* \param tagPath the ordered list of ancestor tag names, parent first
* \param format the target visual format
* \return formatted text
*/
typedef QString (*TagFormatter)(const QStringList &languages,
const QString &tagName,
const QHash<QString, QString> &attributes,
const QString &text,
const QStringList &tagPath,
Kuit::VisualFormat format);
/**
* Get hold of the KUIT setup object for a given domain.
*
* \param domain the translation domain
* \return pointer to KUIT setup object
*/
KI18N_EXPORT KuitSetup &setupForDomain(const QByteArray &domain);
}
class KLocalizedString;
class KuitSetupPrivate;
class KuitFormatterPrivate;
/**
* @class KuitSetup kuitsetup.h <KuitSetup>
*
* Class for modifying KUIT markup in a given domain.
*
* Not directly constructed, but obtained through \c Kuit::setupForDomain.
*/
class KI18N_EXPORT KuitSetup
{
friend KuitSetup &Kuit::setupForDomain(const QByteArray &domain);
friend class KuitFormatterPrivate;
public:
/**
* Destructor.
*/
~KuitSetup();
/**
* Set the formatting string for a tag with attributes combination.
*
* If a new tag name is given, this effectively defines a new tag.
* The same holds for attribute names.
*
* The pattern string \p pattern should contain placeholders
* for inserting the text and the attribute values.
* %1 will be replaced with the wrapped text, and %2 and upwards
* with attribute values in the order given by \p attrNames.
* Non markup-aware translation call with context (\c ki18nc)
* should be used to create the pattern string.
*
* In addition to the pattern, a formatting function
* of the type \c TagFormatter can be given.
* This function receives the full markup parsing context,
* so that it can do whatever is necessary with the wrapped text.
* The result of this function is then substituted into the pattern.
* You can also give an empty pattern (as <tt>KLocalizedString()</tt>)
* together with the formatting function, in which case the function
* is assumed to do everything and no substitution is performed.
*
* \param tagName the name of the tag
* \param attribNames the names of the attributes (empty names are ignored)
* \param format the target visual format
* \param pattern the pattern string
* \param leadingNewlines the number of new lines (\\n) to be maintained
* between any preceding text and the text wrapped
* with this tag (for formats where it matters)
*/
void setTagPattern(const QString &tagName,
const QStringList &attribNames,
Kuit::VisualFormat format,
const KLocalizedString &pattern,
Kuit::TagFormatter formatter = nullptr,
int leadingNewlines = 0);
/**
* Set the KUIT class of the tag.
*
* \param tagName the name of the tag
* \param aClass the KUIT tag class
*/
void setTagClass(const QString &tagName, Kuit::TagClass aClass);
/**
* Set the default visual format for a given UI marker.
*
* Giving <tt>"@<major>"</tt> for \p marker means to set the format
* only for standalone <tt>\@\<major\></tt> marker,
* while <tt>"@<major>:"</tt> (with trailing colon) means to set
* the same format for all <tt>\@\<major\>:\<minor\></tt> combinations.
*
* Defined UI marker major/minor combinations are listed in the section
* \ref uimark_ctxt. If an UI marker combination outside of the defined
* is given as \p marker, it will be ignored.
*
* Setting \c Kuit::UndefinedFormat as \p format
* means to fall back to default format for the given UI marker.
*
* \param marker the UI marker
* \param format the visual format
*/
void setFormatForMarker(const QString &marker, Kuit::VisualFormat format);
private:
KI18N_NO_EXPORT explicit KuitSetup(const QByteArray &domain);
Q_DISABLE_COPY(KuitSetup)
// intentionally not a unique_ptr as this file gets included a lot and using a unique_ptr
// results in too many template instantiations
KuitSetupPrivate *const d;
};
#endif // KUITSETUP_H
@@ -0,0 +1,69 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007, 2013 Chusslove Illich <caslav.ilic@gmx.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KUITSETUP_P_H
#define KUITSETUP_P_H
#include <QString>
class KuitFormatter;
class KuitFormatterPrivate;
namespace Kuit
{
/**
* Convert &, ", ', <, > characters into XML entities
* &amp;, &lt;, &gt;, &apos;, &quot;, respectively.
*/
QString escape(const QString &text);
}
/**
* @internal
* (used by KLocalizedString)
*
* KuitFormatter resolves KUIT markup in user interface text
* into appropriate visual formatting.
*
* @author Chusslove Illich <caslav.ilic@gmx.net>
* @short class for formatting KUIT markup in UI messages
*/
class KuitFormatter
{
public:
/**
* Constructor.
*
* @param language language to create the formatter for
*/
KuitFormatter(const QString &language);
/**
* Transforms KUIT markup in the given text into visual formatting.
* The appropriate visual formatting is decided based on
* the context marker provided in the context string.
*
* @param domain translation domain from which the text was fetched
* @param context context of the text (used if \p format == UndefinedFormat)
* @param text text containing the KUIT markup
* @param format target visual format
* @param isArgument whether this text is inserted into an outer text
*/
QString format(const QByteArray &domain, const QString &context, const QString &text, Kuit::VisualFormat format) const;
/**
* Destructor.
*/
~KuitFormatter();
private:
KuitFormatter(const KuitFormatter &t);
KuitFormatter &operator=(const KuitFormatter &t);
KuitFormatterPrivate *d;
};
#endif
@@ -0,0 +1,124 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2015 Lukáš Tinkl <ltinkl@redhat.com>
SPDX-FileCopyrightText: 2021,2023 Ingo Klöcker <kloecker@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ki18n_logging.h"
#include <QCoreApplication>
#include <QFile>
#include <QLibraryInfo>
#include <QLocale>
#include <QThread>
#include <QTranslator>
#include <memory>
//
// NOTE when changing anything in here, also check whether ECMQmLoader.cpp.in in ECM
// needs to be changed as well!
//
using namespace Qt::Literals;
[[nodiscard]] static QString translationsPath()
{
#ifndef Q_OS_ANDROID
return QLibraryInfo::path(QLibraryInfo::TranslationsPath);
#else
return u"assets://translations/"_s;
#endif
}
static bool loadCatalog(QStringView catalog, QStringView language)
{
Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
const QString fullPath = translationsPath() + '/'_L1 + catalog + language + ".qm"_L1;
if (!QFile::exists(fullPath)) {
return false;
}
auto translator = std::make_unique<QTranslator>(QCoreApplication::instance());
if (!translator->load(fullPath)) {
qCDebug(KI18N) << "Loading catalog failed:" << fullPath;
return false;
}
QCoreApplication::instance()->installTranslator(translator.release());
return true;
}
// load global Qt translation, needed in KDE e.g. by lots of builtin dialogs (QColorDialog, QFontDialog) that we use
static bool loadTranslation(QStringView language)
{
// first, try to load the qt_ meta catalog
if (loadCatalog(u"qt_", language)) {
return true;
}
// if loading the meta catalog failed, then try loading the catalogs
// it depends on, i.e. qtbase, qtmultimedia separately
const auto catalogs = {
u"qtbase_",
u"qtmultimedia_",
};
bool success = false;
for (const auto &catalog : catalogs) {
success |= loadCatalog(catalog, language);
}
return success;
}
static QStringList getSystemLanguages()
{
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
// On Windows and Apple OSs, we cannot use QLocale::system() if an application-specific
// language was set by kxmlgui because Qt ignores LANGUAGE on Windows and Apple OSs.
// The following code is a simplified variant of QSystemLocale::fallbackUiLocale()
// (in qlocale_unix.cpp) ignoring LC_ALL, LC_MESSAGES, and LANG.
if (const auto languages = qEnvironmentVariable("LANGUAGE").split(':'_L1, Qt::SkipEmptyParts); !languages.isEmpty()) {
return languages;
}
#endif
return QLocale::system().uiLanguages();
}
static void load()
{
// The way Qt translation system handles plural forms makes it necessary to
// have a translation file which contains only plural forms for `en`. That's
// why we load the `en` translation unconditionally, then load the
// translation for the current locale to overload it.
QMetaObject::invokeMethod(QCoreApplication::instance(), [] {
loadCatalog(u"qt_", u"en");
auto languages = getSystemLanguages();
for (qsizetype i = 0; i < languages.size(); ++i) {
// normalize into the format used in Qt catalog suffixes
languages[i].replace('-'_L1, '_'_L1);
// make sure we always also have the generic language variant
// depending on the platform that might not be in uiLanguages by default
// insert that after the last country-specific entry for the same language
const auto idx = languages[i].indexOf('_'_L1);
if (idx > 0) {
const QString genericLang = languages[i].left(idx);
qsizetype j = i + 1;
for (; j < languages.size(); ++j) {
if (!languages[j].startsWith(genericLang)) {
break;
}
}
if (languages[j - 1] != genericLang) {
languages.insert(j, genericLang);
}
}
}
languages.removeDuplicates();
for (const auto &language : languages) {
if (language == "en"_L1 || loadTranslation(language)) {
break;
}
}
});
}
Q_COREAPP_STARTUP_FUNCTION(load)
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-FileCopyrightText: 2022 Julius Künzel <jk.kdedev@smartlab.uber.space>
# SPDX-License-Identifier: BSD-3-Clause
ecm_add_qml_module(ki18nlocaledataqmlplugin URI "org.kde.i18n.localeData" VERSION 1.0)
target_sources(ki18nlocaledataqmlplugin PRIVATE
ki18nlocaledataqmlplugin.cpp
)
target_link_libraries(ki18nlocaledataqmlplugin PRIVATE
Qt6::Qml
KF6::I18nLocaleData
)
ecm_finalize_qml_module(ki18nlocaledataqmlplugin DESTINATION ${KDE_INSTALL_QMLDIR})
@@ -0,0 +1,126 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <KCountry>
#include <KCountrySubdivision>
#include <KTimeZone>
#include <QCoreApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlExtensionPlugin>
#include <cstring>
class KI18nLocaleDataQmlPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char *uri) override;
};
// return "undefined" for invalid objects, so JS conditionals work as expected
template<typename T>
static QJSValue toJsValue(T value, QJSEngine *engine)
{
return value.isValid() ? engine->toScriptValue(value) : QJSValue(QJSValue::UndefinedValue);
}
class KCountryFactory
{
Q_GADGET
Q_PROPERTY(QList<KCountry> allCountries READ allCountries)
public:
Q_INVOKABLE QJSValue fromAlpha2(const QString &code) const
{
return toJsValue(KCountry::fromAlpha2(code), m_engine);
}
Q_INVOKABLE QJSValue fromAlpha3(const QString &code) const
{
return toJsValue(KCountry::fromAlpha3(code), m_engine);
}
Q_INVOKABLE QJSValue fromName(const QString &name) const
{
return toJsValue(KCountry::fromName(name), m_engine);
}
Q_INVOKABLE QJSValue fromLocation(double latitude, double longitude) const
{
return toJsValue(KCountry::fromLocation(latitude, longitude), m_engine);
}
QJSEngine *m_engine = nullptr;
private:
QList<KCountry> allCountries() const
{
return KCountry::allCountries();
}
};
class KCountrySubdivisionFactory
{
Q_GADGET
public:
Q_INVOKABLE QJSValue fromCode(const QString &code) const
{
return toJsValue(KCountrySubdivision::fromCode(code), m_engine);
}
Q_INVOKABLE QJSValue fromLocation(double latitude, double longitude) const
{
return toJsValue(KCountrySubdivision::fromLocation(latitude, longitude), m_engine);
}
QJSEngine *m_engine = nullptr;
};
class KTimeZoneWrapper
{
Q_GADGET
public:
Q_INVOKABLE QJSValue fromLocation(double latitude, double longitude) const
{
const auto tzId = KTimeZone::fromLocation(latitude, longitude);
return tzId ? QString::fromUtf8(tzId) : QJSValue(QJSValue::UndefinedValue);
}
Q_INVOKABLE QJSValue country(const QString &tzId) const
{
return toJsValue(KTimeZone::country(tzId.toUtf8()), m_engine);
}
QJSEngine *m_engine = nullptr;
};
void KI18nLocaleDataQmlPlugin::registerTypes(const char *uri)
{
Q_ASSERT(std::strcmp(uri, "org.kde.i18n.localeData") == 0);
qRegisterMetaType<KCountry>();
qRegisterMetaType<KCountrySubdivision>();
qRegisterMetaType<QList<KCountrySubdivision>>();
// HACK qmlplugindump chokes on gadget singletons, to the point of breaking ecm_find_qmlmodule()
if (QCoreApplication::applicationName() != QLatin1String("qmlplugindump")) {
qmlRegisterSingletonType(uri, 1, 0, "Country", [](QQmlEngine *, QJSEngine *engine) -> QJSValue {
KCountryFactory factory;
factory.m_engine = engine;
return engine->toScriptValue(factory);
});
qmlRegisterSingletonType(uri, 1, 0, "CountrySubdivision", [](QQmlEngine *, QJSEngine *engine) -> QJSValue {
KCountrySubdivisionFactory factory;
factory.m_engine = engine;
return engine->toScriptValue(factory);
});
qmlRegisterSingletonType(uri, 1, 0, "TimeZone", [](QQmlEngine *, QJSEngine *engine) -> QJSValue {
KTimeZoneWrapper wrapper;
wrapper.m_engine = engine;
return engine->toScriptValue(wrapper);
});
}
}
#include "ki18nlocaledataqmlplugin.moc"
@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
option(KI18N_EMBEDDED_ISO_CODES_CACHE "Use compiled-in iso-codes data." OFF)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config-localedata.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config-localedata.h")
add_subdirectory(cachegen)
add_library(KF6I18nLocaleData)
add_library(KF6::I18nLocaleData ALIAS KF6I18nLocaleData)
set_target_properties(KF6I18nLocaleData PROPERTIES
VERSION ${KI18N_VERSION}
SOVERSION ${KI18N_SOVERSION}
EXPORT_NAME I18nLocaleData
)
target_sources(KF6I18nLocaleData PRIVATE
isocodes.cpp
isocodescache.cpp
kcountry.cpp
kcountrysubdivision.cpp
ktimezone.cpp
spatial_index.cpp
spatial_index_entry.cpp
spatial_index_property.cpp
timezonedata.cpp
)
ecm_generate_export_header(KF6I18nLocaleData
BASE_NAME KI18nLocaleData
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KI18n
DEPRECATED_BASE_VERSION 0
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
ecm_qt_declare_logging_category(KF6I18nLocaleData
HEADER logging.h
IDENTIFIER KI18NLD
CATEGORY_NAME kf.i18n.localeData
DESCRIPTION "KI18n Locale Data"
EXPORT KI18N
)
if (KI18N_EMBEDDED_ISO_CODES_CACHE)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/iso_3166-1
COMMAND ki18n-iso-codes-cachegen --input ${IsoCodes_PREFIX}/share/iso-codes/json/iso_3166-1.json --output ${CMAKE_CURRENT_BINARY_DIR}/iso_3166-1 --code 3166-1
DEPENDS ${IsoCodes_PREFIX}/share/iso-codes/json/iso_3166-1.json
COMMENT "Generating ISO 3166-1 cache"
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/iso_3166-2
COMMAND ki18n-iso-codes-cachegen --input ${IsoCodes_PREFIX}/share/iso-codes/json/iso_3166-2.json --output ${CMAKE_CURRENT_BINARY_DIR}/iso_3166-2 --code 3166-2
DEPENDS ${IsoCodes_PREFIX}/share/iso-codes/json/iso_3166-2.json
COMMENT "Generating ISO 3166-2 cache"
)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/isocodescache.qrc ${CMAKE_CURRENT_BINARY_DIR}/isocodescache.qrc)
target_sources(KF6I18nLocaleData PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/isocodescache.qrc)
endif()
target_include_directories(KF6I18nLocaleData
INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KI18nLocaleData>"
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KI18n>" # for version header
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>" # for version header
)
target_link_libraries(KF6I18nLocaleData PUBLIC Qt6::Core)
target_link_libraries(KF6I18nLocaleData PRIVATE KF6I18n)
target_compile_options(KF6I18n PRIVATE -DTRANSLATION_DOMAIN=\"ki18n6\")
install(TARGETS KF6I18nLocaleData EXPORT KF6I18nTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
ecm_generate_headers(KI18nLocaleData_HEADERS
HEADER_NAMES
KCountry
KCountrySubdivision
KTimeZone
REQUIRED_HEADERS KI18nLocaleData_HEADERS
)
install(FILES
${KI18nLocaleData_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/ki18nlocaledata_export.h
DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF}/KI18nLocaleData" COMPONENT Devel
)
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
if (CMAKE_CROSSCOMPILING)
return()
endif()
add_executable(ki18n-iso-codes-cachegen)
target_include_directories(ki18n-iso-codes-cachegen PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
target_sources(ki18n-iso-codes-cachegen PRIVATE
cachegen.cpp
../isocodescache.cpp
)
ecm_qt_declare_logging_category(ki18n-iso-codes-cachegen
HEADER logging.h
IDENTIFIER KI18NLD
CATEGORY_NAME kf.i18n.localeData
DESCRIPTION "KI18n Locale Data"
)
target_link_libraries(ki18n-iso-codes-cachegen PRIVATE
Qt6::Core
)
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "../isocodescache_p.h"
#include <QCommandLineParser>
#include <QCoreApplication>
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCommandLineParser parser;
QCommandLineOption codeOpt(QStringLiteral("code"), QStringLiteral("ISO code type to generate a cache for [3166-1, 3166-2]"), QStringLiteral("code"));
parser.addOption(codeOpt);
QCommandLineOption inputOpt(QStringLiteral("input"), QStringLiteral("Input ISO codes JSON file to generate the cache for."), QStringLiteral("input"));
parser.addOption(inputOpt);
QCommandLineOption outputOpt(QStringLiteral("output"), QStringLiteral("Generated cache file."), QStringLiteral("output"));
parser.addOption(outputOpt);
parser.addHelpOption();
parser.process(app);
const QString code = parser.value(codeOpt);
const QString inputFile = parser.value(inputOpt);
const QString outputFile = parser.value(outputOpt);
if (code == QLatin1String("3166-1")) {
IsoCodesCache::createIso3166_1Cache(inputFile, outputFile);
} else if (code == QLatin1String("3166-2")) {
IsoCodesCache::createIso3166_2Cache(inputFile, outputFile);
} else {
parser.showHelp();
return 1;
}
return 0;
}
@@ -0,0 +1,12 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KI18NLOCALEDATA_CONFIG_H
#define KI18NLOCALEDATA_CONFIG_H
#define ISO_CODES_PREFIX "@IsoCodes_PREFIX@"
#endif
@@ -0,0 +1,227 @@
/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#include "isocodes_p.h"
#include "mapentry_p.h"
#include "timezone_names_p.h"
static constexpr const MapEntry<uint16_t> country_timezone_map[] = {
{IsoCodes::alpha2CodeToKey("AD"), Tz::Europe_Andorra},
{IsoCodes::alpha2CodeToKey("AE"), Tz::Asia_Dubai},
{IsoCodes::alpha2CodeToKey("AF"), Tz::Asia_Kabul},
{IsoCodes::alpha2CodeToKey("AG"), Tz::America_Antigua},
{IsoCodes::alpha2CodeToKey("AI"), Tz::America_Anguilla},
{IsoCodes::alpha2CodeToKey("AL"), Tz::Europe_Tirane},
{IsoCodes::alpha2CodeToKey("AM"), Tz::Asia_Yerevan},
{IsoCodes::alpha2CodeToKey("AO"), Tz::Africa_Luanda},
{IsoCodes::alpha2CodeToKey("AR"), Tz::America_Argentina_Buenos_Aires},
{IsoCodes::alpha2CodeToKey("AS"), Tz::Pacific_Pago_Pago},
{IsoCodes::alpha2CodeToKey("AT"), Tz::Europe_Vienna},
{IsoCodes::alpha2CodeToKey("AW"), Tz::America_Aruba},
{IsoCodes::alpha2CodeToKey("AX"), Tz::Europe_Helsinki},
{IsoCodes::alpha2CodeToKey("AZ"), Tz::Asia_Baku},
{IsoCodes::alpha2CodeToKey("BA"), Tz::Europe_Sarajevo},
{IsoCodes::alpha2CodeToKey("BB"), Tz::America_Barbados},
{IsoCodes::alpha2CodeToKey("BD"), Tz::Asia_Dhaka},
{IsoCodes::alpha2CodeToKey("BE"), Tz::Europe_Brussels},
{IsoCodes::alpha2CodeToKey("BF"), Tz::Africa_Ouagadougou},
{IsoCodes::alpha2CodeToKey("BG"), Tz::Europe_Sofia},
{IsoCodes::alpha2CodeToKey("BH"), Tz::Asia_Bahrain},
{IsoCodes::alpha2CodeToKey("BI"), Tz::Africa_Bujumbura},
{IsoCodes::alpha2CodeToKey("BJ"), Tz::Africa_Porto_Novo},
{IsoCodes::alpha2CodeToKey("BL"), Tz::America_St_Barthelemy},
{IsoCodes::alpha2CodeToKey("BM"), Tz::Atlantic_Bermuda},
{IsoCodes::alpha2CodeToKey("BN"), Tz::Asia_Brunei},
{IsoCodes::alpha2CodeToKey("BO"), Tz::America_La_Paz},
{IsoCodes::alpha2CodeToKey("BQ"), Tz::America_Kralendijk},
{IsoCodes::alpha2CodeToKey("BS"), Tz::America_Nassau},
{IsoCodes::alpha2CodeToKey("BT"), Tz::Asia_Thimphu},
{IsoCodes::alpha2CodeToKey("BW"), Tz::Africa_Gaborone},
{IsoCodes::alpha2CodeToKey("BY"), Tz::Europe_Minsk},
{IsoCodes::alpha2CodeToKey("BZ"), Tz::America_Belize},
{IsoCodes::alpha2CodeToKey("CC"), Tz::Indian_Cocos},
{IsoCodes::alpha2CodeToKey("CF"), Tz::Africa_Bangui},
{IsoCodes::alpha2CodeToKey("CG"), Tz::Africa_Brazzaville},
{IsoCodes::alpha2CodeToKey("CH"), Tz::Europe_Zurich},
{IsoCodes::alpha2CodeToKey("CI"), Tz::Africa_Abidjan},
{IsoCodes::alpha2CodeToKey("CK"), Tz::Pacific_Rarotonga},
{IsoCodes::alpha2CodeToKey("CM"), Tz::Africa_Douala},
{IsoCodes::alpha2CodeToKey("CO"), Tz::America_Bogota},
{IsoCodes::alpha2CodeToKey("CR"), Tz::America_Costa_Rica},
{IsoCodes::alpha2CodeToKey("CU"), Tz::America_Havana},
{IsoCodes::alpha2CodeToKey("CV"), Tz::Atlantic_Cape_Verde},
{IsoCodes::alpha2CodeToKey("CW"), Tz::America_Curacao},
{IsoCodes::alpha2CodeToKey("CX"), Tz::Indian_Christmas},
{IsoCodes::alpha2CodeToKey("CY"), Tz::Asia_Nicosia},
{IsoCodes::alpha2CodeToKey("CZ"), Tz::Europe_Prague},
{IsoCodes::alpha2CodeToKey("DE"), Tz::Europe_Berlin},
{IsoCodes::alpha2CodeToKey("DJ"), Tz::Africa_Djibouti},
{IsoCodes::alpha2CodeToKey("DK"), Tz::Europe_Copenhagen},
{IsoCodes::alpha2CodeToKey("DM"), Tz::America_Dominica},
{IsoCodes::alpha2CodeToKey("DO"), Tz::America_Santo_Domingo},
{IsoCodes::alpha2CodeToKey("DZ"), Tz::Africa_Algiers},
{IsoCodes::alpha2CodeToKey("EE"), Tz::Europe_Tallinn},
{IsoCodes::alpha2CodeToKey("EG"), Tz::Africa_Cairo},
{IsoCodes::alpha2CodeToKey("EH"), Tz::Africa_El_Aaiun},
{IsoCodes::alpha2CodeToKey("ER"), Tz::Africa_Asmara},
{IsoCodes::alpha2CodeToKey("ET"), Tz::Africa_Addis_Ababa},
{IsoCodes::alpha2CodeToKey("FI"), Tz::Europe_Helsinki},
{IsoCodes::alpha2CodeToKey("FJ"), Tz::Pacific_Fiji},
{IsoCodes::alpha2CodeToKey("FK"), Tz::Atlantic_Stanley},
{IsoCodes::alpha2CodeToKey("FO"), Tz::Atlantic_Faroe},
{IsoCodes::alpha2CodeToKey("GA"), Tz::Africa_Libreville},
{IsoCodes::alpha2CodeToKey("GB"), Tz::Europe_London},
{IsoCodes::alpha2CodeToKey("GD"), Tz::America_Grenada},
{IsoCodes::alpha2CodeToKey("GF"), Tz::America_Cayenne},
{IsoCodes::alpha2CodeToKey("GG"), Tz::Europe_Guernsey},
{IsoCodes::alpha2CodeToKey("GH"), Tz::Africa_Accra},
{IsoCodes::alpha2CodeToKey("GI"), Tz::Europe_Gibraltar},
{IsoCodes::alpha2CodeToKey("GM"), Tz::Africa_Banjul},
{IsoCodes::alpha2CodeToKey("GN"), Tz::Africa_Conakry},
{IsoCodes::alpha2CodeToKey("GP"), Tz::America_Guadeloupe},
{IsoCodes::alpha2CodeToKey("GQ"), Tz::Africa_Malabo},
{IsoCodes::alpha2CodeToKey("GR"), Tz::Europe_Athens},
{IsoCodes::alpha2CodeToKey("GS"), Tz::Atlantic_South_Georgia},
{IsoCodes::alpha2CodeToKey("GT"), Tz::America_Guatemala},
{IsoCodes::alpha2CodeToKey("GU"), Tz::Pacific_Guam},
{IsoCodes::alpha2CodeToKey("GW"), Tz::Africa_Bissau},
{IsoCodes::alpha2CodeToKey("GY"), Tz::America_Guyana},
{IsoCodes::alpha2CodeToKey("HK"), Tz::Asia_Hong_Kong},
{IsoCodes::alpha2CodeToKey("HN"), Tz::America_Tegucigalpa},
{IsoCodes::alpha2CodeToKey("HR"), Tz::Europe_Zagreb},
{IsoCodes::alpha2CodeToKey("HT"), Tz::America_Port_au_Prince},
{IsoCodes::alpha2CodeToKey("HU"), Tz::Europe_Budapest},
{IsoCodes::alpha2CodeToKey("IE"), Tz::Europe_Dublin},
{IsoCodes::alpha2CodeToKey("IL"), Tz::Asia_Jerusalem},
{IsoCodes::alpha2CodeToKey("IM"), Tz::Europe_Isle_of_Man},
{IsoCodes::alpha2CodeToKey("IN"), Tz::Asia_Kolkata},
{IsoCodes::alpha2CodeToKey("IO"), Tz::Indian_Chagos},
{IsoCodes::alpha2CodeToKey("IQ"), Tz::Asia_Baghdad},
{IsoCodes::alpha2CodeToKey("IR"), Tz::Asia_Tehran},
{IsoCodes::alpha2CodeToKey("IS"), Tz::Atlantic_Reykjavik},
{IsoCodes::alpha2CodeToKey("IT"), Tz::Europe_Rome},
{IsoCodes::alpha2CodeToKey("JE"), Tz::Europe_Jersey},
{IsoCodes::alpha2CodeToKey("JM"), Tz::America_Jamaica},
{IsoCodes::alpha2CodeToKey("JO"), Tz::Asia_Amman},
{IsoCodes::alpha2CodeToKey("JP"), Tz::Asia_Tokyo},
{IsoCodes::alpha2CodeToKey("KE"), Tz::Africa_Nairobi},
{IsoCodes::alpha2CodeToKey("KG"), Tz::Asia_Bishkek},
{IsoCodes::alpha2CodeToKey("KH"), Tz::Asia_Phnom_Penh},
{IsoCodes::alpha2CodeToKey("KM"), Tz::Indian_Comoro},
{IsoCodes::alpha2CodeToKey("KN"), Tz::America_St_Kitts},
{IsoCodes::alpha2CodeToKey("KP"), Tz::Asia_Pyongyang},
{IsoCodes::alpha2CodeToKey("KR"), Tz::Asia_Seoul},
{IsoCodes::alpha2CodeToKey("KW"), Tz::Asia_Kuwait},
{IsoCodes::alpha2CodeToKey("KY"), Tz::America_Cayman},
{IsoCodes::alpha2CodeToKey("LA"), Tz::Asia_Vientiane},
{IsoCodes::alpha2CodeToKey("LB"), Tz::Asia_Beirut},
{IsoCodes::alpha2CodeToKey("LC"), Tz::America_St_Lucia},
{IsoCodes::alpha2CodeToKey("LI"), Tz::Europe_Vaduz},
{IsoCodes::alpha2CodeToKey("LK"), Tz::Asia_Colombo},
{IsoCodes::alpha2CodeToKey("LR"), Tz::Africa_Monrovia},
{IsoCodes::alpha2CodeToKey("LS"), Tz::Africa_Maseru},
{IsoCodes::alpha2CodeToKey("LT"), Tz::Europe_Vilnius},
{IsoCodes::alpha2CodeToKey("LU"), Tz::Europe_Luxembourg},
{IsoCodes::alpha2CodeToKey("LV"), Tz::Europe_Riga},
{IsoCodes::alpha2CodeToKey("LY"), Tz::Africa_Tripoli},
{IsoCodes::alpha2CodeToKey("MC"), Tz::Europe_Monaco},
{IsoCodes::alpha2CodeToKey("MD"), Tz::Europe_Chisinau},
{IsoCodes::alpha2CodeToKey("ME"), Tz::Europe_Podgorica},
{IsoCodes::alpha2CodeToKey("MF"), Tz::America_Marigot},
{IsoCodes::alpha2CodeToKey("MG"), Tz::Indian_Antananarivo},
{IsoCodes::alpha2CodeToKey("MK"), Tz::Europe_Skopje},
{IsoCodes::alpha2CodeToKey("ML"), Tz::Africa_Bamako},
{IsoCodes::alpha2CodeToKey("MM"), Tz::Asia_Yangon},
{IsoCodes::alpha2CodeToKey("MO"), Tz::Asia_Macau},
{IsoCodes::alpha2CodeToKey("MP"), Tz::Pacific_Saipan},
{IsoCodes::alpha2CodeToKey("MQ"), Tz::America_Martinique},
{IsoCodes::alpha2CodeToKey("MR"), Tz::Africa_Nouakchott},
{IsoCodes::alpha2CodeToKey("MS"), Tz::America_Montserrat},
{IsoCodes::alpha2CodeToKey("MT"), Tz::Europe_Malta},
{IsoCodes::alpha2CodeToKey("MU"), Tz::Indian_Mauritius},
{IsoCodes::alpha2CodeToKey("MV"), Tz::Indian_Maldives},
{IsoCodes::alpha2CodeToKey("MW"), Tz::Africa_Blantyre},
{IsoCodes::alpha2CodeToKey("MY"), Tz::Asia_Kuala_Lumpur},
{IsoCodes::alpha2CodeToKey("MZ"), Tz::Africa_Maputo},
{IsoCodes::alpha2CodeToKey("NA"), Tz::Africa_Windhoek},
{IsoCodes::alpha2CodeToKey("NC"), Tz::Pacific_Noumea},
{IsoCodes::alpha2CodeToKey("NE"), Tz::Africa_Niamey},
{IsoCodes::alpha2CodeToKey("NF"), Tz::Pacific_Norfolk},
{IsoCodes::alpha2CodeToKey("NG"), Tz::Africa_Lagos},
{IsoCodes::alpha2CodeToKey("NI"), Tz::America_Managua},
{IsoCodes::alpha2CodeToKey("NO"), Tz::Europe_Oslo},
{IsoCodes::alpha2CodeToKey("NP"), Tz::Asia_Kathmandu},
{IsoCodes::alpha2CodeToKey("NR"), Tz::Pacific_Nauru},
{IsoCodes::alpha2CodeToKey("NU"), Tz::Pacific_Niue},
{IsoCodes::alpha2CodeToKey("OM"), Tz::Asia_Muscat},
{IsoCodes::alpha2CodeToKey("PA"), Tz::America_Panama},
{IsoCodes::alpha2CodeToKey("PE"), Tz::America_Lima},
{IsoCodes::alpha2CodeToKey("PH"), Tz::Asia_Manila},
{IsoCodes::alpha2CodeToKey("PK"), Tz::Asia_Karachi},
{IsoCodes::alpha2CodeToKey("PL"), Tz::Europe_Warsaw},
{IsoCodes::alpha2CodeToKey("PM"), Tz::America_Miquelon},
{IsoCodes::alpha2CodeToKey("PN"), Tz::Pacific_Pitcairn},
{IsoCodes::alpha2CodeToKey("PR"), Tz::America_Puerto_Rico},
{IsoCodes::alpha2CodeToKey("PW"), Tz::Pacific_Palau},
{IsoCodes::alpha2CodeToKey("PY"), Tz::America_Asuncion},
{IsoCodes::alpha2CodeToKey("QA"), Tz::Asia_Qatar},
{IsoCodes::alpha2CodeToKey("RE"), Tz::Indian_Reunion},
{IsoCodes::alpha2CodeToKey("RO"), Tz::Europe_Bucharest},
{IsoCodes::alpha2CodeToKey("RS"), Tz::Europe_Belgrade},
{IsoCodes::alpha2CodeToKey("RW"), Tz::Africa_Kigali},
{IsoCodes::alpha2CodeToKey("SA"), Tz::Asia_Riyadh},
{IsoCodes::alpha2CodeToKey("SB"), Tz::Pacific_Guadalcanal},
{IsoCodes::alpha2CodeToKey("SC"), Tz::Indian_Mahe},
{IsoCodes::alpha2CodeToKey("SE"), Tz::Europe_Stockholm},
{IsoCodes::alpha2CodeToKey("SG"), Tz::Asia_Singapore},
{IsoCodes::alpha2CodeToKey("SH"), Tz::Atlantic_St_Helena},
{IsoCodes::alpha2CodeToKey("SI"), Tz::Europe_Ljubljana},
{IsoCodes::alpha2CodeToKey("SJ"), Tz::Europe_Oslo},
{IsoCodes::alpha2CodeToKey("SK"), Tz::Europe_Bratislava},
{IsoCodes::alpha2CodeToKey("SL"), Tz::Africa_Freetown},
{IsoCodes::alpha2CodeToKey("SM"), Tz::Europe_San_Marino},
{IsoCodes::alpha2CodeToKey("SN"), Tz::Africa_Dakar},
{IsoCodes::alpha2CodeToKey("SO"), Tz::Africa_Mogadishu},
{IsoCodes::alpha2CodeToKey("SR"), Tz::America_Paramaribo},
{IsoCodes::alpha2CodeToKey("ST"), Tz::Africa_Sao_Tome},
{IsoCodes::alpha2CodeToKey("SV"), Tz::America_El_Salvador},
{IsoCodes::alpha2CodeToKey("SX"), Tz::America_Lower_Princes},
{IsoCodes::alpha2CodeToKey("SY"), Tz::Asia_Damascus},
{IsoCodes::alpha2CodeToKey("SZ"), Tz::Africa_Mbabane},
{IsoCodes::alpha2CodeToKey("TC"), Tz::America_Grand_Turk},
{IsoCodes::alpha2CodeToKey("TD"), Tz::Africa_Ndjamena},
{IsoCodes::alpha2CodeToKey("TF"), Tz::Indian_Kerguelen},
{IsoCodes::alpha2CodeToKey("TG"), Tz::Africa_Lome},
{IsoCodes::alpha2CodeToKey("TH"), Tz::Asia_Bangkok},
{IsoCodes::alpha2CodeToKey("TJ"), Tz::Asia_Dushanbe},
{IsoCodes::alpha2CodeToKey("TK"), Tz::Pacific_Fakaofo},
{IsoCodes::alpha2CodeToKey("TL"), Tz::Asia_Dili},
{IsoCodes::alpha2CodeToKey("TM"), Tz::Asia_Ashgabat},
{IsoCodes::alpha2CodeToKey("TN"), Tz::Africa_Tunis},
{IsoCodes::alpha2CodeToKey("TO"), Tz::Pacific_Tongatapu},
{IsoCodes::alpha2CodeToKey("TR"), Tz::Europe_Istanbul},
{IsoCodes::alpha2CodeToKey("TT"), Tz::America_Port_of_Spain},
{IsoCodes::alpha2CodeToKey("TV"), Tz::Pacific_Funafuti},
{IsoCodes::alpha2CodeToKey("TW"), Tz::Asia_Taipei},
{IsoCodes::alpha2CodeToKey("TZ"), Tz::Africa_Dar_es_Salaam},
{IsoCodes::alpha2CodeToKey("UG"), Tz::Africa_Kampala},
{IsoCodes::alpha2CodeToKey("UY"), Tz::America_Montevideo},
{IsoCodes::alpha2CodeToKey("VA"), Tz::Europe_Vatican},
{IsoCodes::alpha2CodeToKey("VC"), Tz::America_St_Vincent},
{IsoCodes::alpha2CodeToKey("VE"), Tz::America_Caracas},
{IsoCodes::alpha2CodeToKey("VG"), Tz::America_Tortola},
{IsoCodes::alpha2CodeToKey("VI"), Tz::America_St_Thomas},
{IsoCodes::alpha2CodeToKey("VU"), Tz::Pacific_Efate},
{IsoCodes::alpha2CodeToKey("WF"), Tz::Pacific_Wallis},
{IsoCodes::alpha2CodeToKey("WS"), Tz::Pacific_Apia},
{IsoCodes::alpha2CodeToKey("XK"), Tz::Europe_Belgrade},
{IsoCodes::alpha2CodeToKey("YE"), Tz::Asia_Aden},
{IsoCodes::alpha2CodeToKey("YT"), Tz::Indian_Mayotte},
{IsoCodes::alpha2CodeToKey("ZA"), Tz::Africa_Johannesburg},
{IsoCodes::alpha2CodeToKey("ZM"), Tz::Africa_Lusaka},
{IsoCodes::alpha2CodeToKey("ZW"), Tz::Africa_Harare},
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: CC0-1.0
* SPDX-FileCopyrightText: none
*
* Autogenerated spatial index generated using QGIS.
*/
#include <cstdint>
constexpr const float XStart = -180;
constexpr const float XRange = 360;
constexpr const float YStart = -60;
constexpr const float YRange = 140;
constexpr const uint8_t ZDepth = 11;
@@ -0,0 +1,827 @@
/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#include "isocodes_p.h"
#include "mapentry_p.h"
#include "timezone_names_p.h"
static constexpr const MapEntry<uint32_t> subdivision_timezone_map[] = {
{IsoCodes::subdivisionCodeToKey("AU-ACT"), Tz::Australia_Sydney},
{IsoCodes::subdivisionCodeToKey("AU-NSW"), Tz::Australia_Sydney},
{IsoCodes::subdivisionCodeToKey("AU-NSW"), Tz::Australia_Broken_Hill},
{IsoCodes::subdivisionCodeToKey("AU-NSW"), Tz::Australia_Lord_Howe},
{IsoCodes::subdivisionCodeToKey("AU-NT"), Tz::Australia_Darwin},
{IsoCodes::subdivisionCodeToKey("AU-QLD"), Tz::Australia_Brisbane},
{IsoCodes::subdivisionCodeToKey("AU-QLD"), Tz::Australia_Lindeman},
{IsoCodes::subdivisionCodeToKey("AU-SA"), Tz::Australia_Adelaide},
{IsoCodes::subdivisionCodeToKey("AU-TAS"), Tz::Australia_Hobart},
{IsoCodes::subdivisionCodeToKey("AU-TAS"), Tz::Australia_Currie},
{IsoCodes::subdivisionCodeToKey("AU-TAS"), Tz::Antarctica_Macquarie},
{IsoCodes::subdivisionCodeToKey("AU-VIC"), Tz::Australia_Melbourne},
{IsoCodes::subdivisionCodeToKey("AU-WA"), Tz::Australia_Perth},
{IsoCodes::subdivisionCodeToKey("AU-WA"), Tz::Australia_Eucla},
{IsoCodes::subdivisionCodeToKey("BR-AC"), Tz::America_Rio_Branco},
{IsoCodes::subdivisionCodeToKey("BR-AL"), Tz::America_Maceio},
{IsoCodes::subdivisionCodeToKey("BR-AM"), Tz::America_Manaus},
{IsoCodes::subdivisionCodeToKey("BR-AM"), Tz::America_Eirunepe},
{IsoCodes::subdivisionCodeToKey("BR-AP"), Tz::America_Belem},
{IsoCodes::subdivisionCodeToKey("BR-BA"), Tz::America_Bahia},
{IsoCodes::subdivisionCodeToKey("BR-DF"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-ES"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-GO"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-MA"), Tz::America_Fortaleza},
{IsoCodes::subdivisionCodeToKey("BR-MG"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-MS"), Tz::America_Campo_Grande},
{IsoCodes::subdivisionCodeToKey("BR-MT"), Tz::America_Cuiaba},
{IsoCodes::subdivisionCodeToKey("BR-PA"), Tz::America_Santarem},
{IsoCodes::subdivisionCodeToKey("BR-PA"), Tz::America_Belem},
{IsoCodes::subdivisionCodeToKey("BR-PB"), Tz::America_Fortaleza},
{IsoCodes::subdivisionCodeToKey("BR-PE"), Tz::America_Recife},
{IsoCodes::subdivisionCodeToKey("BR-PE"), Tz::America_Noronha},
{IsoCodes::subdivisionCodeToKey("BR-PI"), Tz::America_Fortaleza},
{IsoCodes::subdivisionCodeToKey("BR-RJ"), Tz::America_Sao_Paulo},
// BR-RN
{IsoCodes::subdivisionCodeToKey("BR-RO"), Tz::America_Porto_Velho},
{IsoCodes::subdivisionCodeToKey("BR-RR"), Tz::America_Boa_Vista},
{IsoCodes::subdivisionCodeToKey("BR-RS"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-SE"), Tz::America_Maceio},
{IsoCodes::subdivisionCodeToKey("BR-SP"), Tz::America_Sao_Paulo},
{IsoCodes::subdivisionCodeToKey("BR-TO"), Tz::America_Araguaina},
{IsoCodes::subdivisionCodeToKey("CA-AB"), Tz::America_Edmonton},
{IsoCodes::subdivisionCodeToKey("CA-BC"), Tz::America_Vancouver},
{IsoCodes::subdivisionCodeToKey("CA-BC"), Tz::America_Fort_Nelson},
{IsoCodes::subdivisionCodeToKey("CA-BC"), Tz::America_Dawson_Creek},
{IsoCodes::subdivisionCodeToKey("CA-BC"), Tz::America_Edmonton},
{IsoCodes::subdivisionCodeToKey("CA-BC"), Tz::America_Creston},
{IsoCodes::subdivisionCodeToKey("CA-MB"), Tz::America_Winnipeg},
{IsoCodes::subdivisionCodeToKey("CA-NB"), Tz::America_Moncton},
{IsoCodes::subdivisionCodeToKey("CA-NL"), Tz::America_Goose_Bay},
{IsoCodes::subdivisionCodeToKey("CA-NL"), Tz::America_St_Johns},
{IsoCodes::subdivisionCodeToKey("CA-NS"), Tz::America_Halifax},
{IsoCodes::subdivisionCodeToKey("CA-NS"), Tz::America_Glace_Bay},
{IsoCodes::subdivisionCodeToKey("CA-NT"), Tz::America_Yellowknife},
{IsoCodes::subdivisionCodeToKey("CA-NT"), Tz::America_Inuvik},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Iqaluit},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Rankin_Inlet},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Cambridge_Bay},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Pangnirtung},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Atikokan},
{IsoCodes::subdivisionCodeToKey("CA-NU"), Tz::America_Resolute},
{IsoCodes::subdivisionCodeToKey("CA-ON"), Tz::America_Toronto},
{IsoCodes::subdivisionCodeToKey("CA-ON"), Tz::America_Rainy_River},
{IsoCodes::subdivisionCodeToKey("CA-ON"), Tz::America_Atikokan},
{IsoCodes::subdivisionCodeToKey("CA-ON"), Tz::America_Thunder_Bay},
{IsoCodes::subdivisionCodeToKey("CA-PE"), Tz::America_Halifax},
{IsoCodes::subdivisionCodeToKey("CA-QC"), Tz::America_Toronto},
{IsoCodes::subdivisionCodeToKey("CA-QC"), Tz::America_Blanc_Sablon},
{IsoCodes::subdivisionCodeToKey("CA-QC"), Tz::America_Halifax},
{IsoCodes::subdivisionCodeToKey("CA-SK"), Tz::America_Regina},
{IsoCodes::subdivisionCodeToKey("CA-SK"), Tz::America_Swift_Current},
{IsoCodes::subdivisionCodeToKey("CA-YT"), Tz::America_Whitehorse},
{IsoCodes::subdivisionCodeToKey("CA-YT"), Tz::America_Dawson},
{IsoCodes::subdivisionCodeToKey("CD-BC"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-BU"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-EQ"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-HK"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-HL"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-HU"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-IT"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-KC"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-KE"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-KG"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-KL"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-KN"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-KS"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-LO"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-LU"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-MA"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-MN"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-MO"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-NK"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-NU"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-SA"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-SK"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-SU"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CD-TA"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-TO"), Tz::Africa_Lubumbashi},
{IsoCodes::subdivisionCodeToKey("CD-TU"), Tz::Africa_Kinshasa},
{IsoCodes::subdivisionCodeToKey("CL-AI"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-AN"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-AP"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-AR"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-AT"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-BI"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-CO"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-LI"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-LL"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-LR"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-MA"), Tz::America_Punta_Arenas},
{IsoCodes::subdivisionCodeToKey("CL-ML"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-NB"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-RM"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-TA"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-VS"), Tz::America_Santiago},
{IsoCodes::subdivisionCodeToKey("CL-VS"), Tz::Pacific_Easter},
{IsoCodes::subdivisionCodeToKey("CN-AH"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-BJ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-CQ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-FJ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-GD"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-GS"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-GX"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-GZ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HA"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HB"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HE"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HI"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HK"), Tz::Asia_Hong_Kong},
{IsoCodes::subdivisionCodeToKey("CN-HL"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-HN"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-JL"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-JS"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-JX"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-LN"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-MO"), Tz::Asia_Macau},
{IsoCodes::subdivisionCodeToKey("CN-NM"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-NX"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-QH"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-SC"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-SD"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-SH"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-SN"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-SX"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-TJ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-XJ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-XJ"), Tz::Asia_Urumqi},
{IsoCodes::subdivisionCodeToKey("CN-XZ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-XZ"), Tz::Asia_Thimphu},
{IsoCodes::subdivisionCodeToKey("CN-YN"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("CN-ZJ"), Tz::Asia_Shanghai},
{IsoCodes::subdivisionCodeToKey("EC-A"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-B"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-C"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-D"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-E"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-F"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-G"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-H"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-I"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-L"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-M"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-N"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-O"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-P"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-R"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-S"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-SD"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-SE"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-T"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-U"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-W"), Tz::Pacific_Galapagos},
{IsoCodes::subdivisionCodeToKey("EC-X"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-Y"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("EC-Z"), Tz::America_Guayaquil},
{IsoCodes::subdivisionCodeToKey("ES-AN"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-AR"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-AS"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-CB"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-CE"), Tz::Africa_Ceuta},
{IsoCodes::subdivisionCodeToKey("ES-CL"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-CM"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-CN"), Tz::Atlantic_Canary},
{IsoCodes::subdivisionCodeToKey("ES-CT"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-EX"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-GA"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-IB"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-MC"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-MD"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-ML"), Tz::Africa_Ceuta},
{IsoCodes::subdivisionCodeToKey("ES-NC"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-PV"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-RI"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("ES-VC"), Tz::Europe_Madrid},
{IsoCodes::subdivisionCodeToKey("FM-KSA"), Tz::Pacific_Kosrae},
{IsoCodes::subdivisionCodeToKey("FM-PNI"), Tz::Pacific_Pohnpei},
{IsoCodes::subdivisionCodeToKey("FM-TRK"), Tz::Pacific_Chuuk},
{IsoCodes::subdivisionCodeToKey("FM-YAP"), Tz::Pacific_Chuuk},
{IsoCodes::subdivisionCodeToKey("FR-ARA"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-BFC"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-BL"), Tz::America_St_Barthelemy},
{IsoCodes::subdivisionCodeToKey("FR-BRE"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-COR"), Tz::Europe_Paris},
// FR-CP
{IsoCodes::subdivisionCodeToKey("FR-CVL"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-GES"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-GF"), Tz::America_Cayenne},
{IsoCodes::subdivisionCodeToKey("FR-GUA"), Tz::America_Guadeloupe},
{IsoCodes::subdivisionCodeToKey("FR-HDF"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-IDF"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-LRE"), Tz::Indian_Reunion},
{IsoCodes::subdivisionCodeToKey("FR-MAY"), Tz::Indian_Mayotte},
{IsoCodes::subdivisionCodeToKey("FR-MF"), Tz::America_Marigot},
{IsoCodes::subdivisionCodeToKey("FR-MQ"), Tz::America_Martinique},
{IsoCodes::subdivisionCodeToKey("FR-NAQ"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-NC"), Tz::Pacific_Noumea},
{IsoCodes::subdivisionCodeToKey("FR-NOR"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-OCC"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-PAC"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-PDL"), Tz::Europe_Paris},
{IsoCodes::subdivisionCodeToKey("FR-PF"), Tz::Pacific_Tahiti},
{IsoCodes::subdivisionCodeToKey("FR-PF"), Tz::Pacific_Marquesas},
{IsoCodes::subdivisionCodeToKey("FR-PF"), Tz::Pacific_Gambier},
{IsoCodes::subdivisionCodeToKey("FR-PM"), Tz::America_Miquelon},
{IsoCodes::subdivisionCodeToKey("FR-TF"), Tz::Indian_Kerguelen},
{IsoCodes::subdivisionCodeToKey("FR-WF"), Tz::Pacific_Wallis},
{IsoCodes::subdivisionCodeToKey("GE-AB"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-AB"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("GE-AJ"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-GU"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-IM"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-KA"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-KK"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-MM"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-RL"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-SJ"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-SK"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-SZ"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GE-TB"), Tz::Asia_Tbilisi},
{IsoCodes::subdivisionCodeToKey("GL-AV"), Tz::America_Nuuk},
{IsoCodes::subdivisionCodeToKey("GL-AV"), Tz::America_Thule},
{IsoCodes::subdivisionCodeToKey("GL-KU"), Tz::America_Nuuk},
{IsoCodes::subdivisionCodeToKey("GL-QE"), Tz::America_Nuuk},
{IsoCodes::subdivisionCodeToKey("GL-QT"), Tz::America_Nuuk},
{IsoCodes::subdivisionCodeToKey("GL-SM"), Tz::America_Nuuk},
{IsoCodes::subdivisionCodeToKey("GL-SM"), Tz::America_Scoresbysund},
{IsoCodes::subdivisionCodeToKey("ID-AC"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-BA"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-BB"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-BE"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-BT"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-GO"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-JA"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-JB"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-JI"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-JK"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-JT"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-JW"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-KA"), Tz::Asia_Pontianak},
{IsoCodes::subdivisionCodeToKey("ID-KA"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-KB"), Tz::Asia_Pontianak},
{IsoCodes::subdivisionCodeToKey("ID-KI"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-KR"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-KS"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-KT"), Tz::Asia_Pontianak},
{IsoCodes::subdivisionCodeToKey("ID-KU"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-LA"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-MA"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-ML"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-MU"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-NB"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-NT"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-NT"), Tz::Asia_Dili},
{IsoCodes::subdivisionCodeToKey("ID-NU"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-NU"), Tz::Asia_Dili},
{IsoCodes::subdivisionCodeToKey("ID-PA"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-PB"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-PP"), Tz::Asia_Jayapura},
{IsoCodes::subdivisionCodeToKey("ID-RI"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-SA"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SB"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-SG"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SL"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SM"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-SN"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SR"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SS"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-ST"), Tz::Asia_Makassar},
{IsoCodes::subdivisionCodeToKey("ID-SU"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("ID-YO"), Tz::Asia_Jakarta},
{IsoCodes::subdivisionCodeToKey("KI-G"), Tz::Pacific_Tarawa},
{IsoCodes::subdivisionCodeToKey("KI-L"), Tz::Pacific_Kiritimati},
{IsoCodes::subdivisionCodeToKey("KI-P"), Tz::Pacific_Enderbury},
{IsoCodes::subdivisionCodeToKey("KZ-AKM"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-AKT"), Tz::Asia_Aqtobe},
{IsoCodes::subdivisionCodeToKey("KZ-ALA"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-ALM"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-AST"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-ATY"), Tz::Asia_Atyrau},
{IsoCodes::subdivisionCodeToKey("KZ-BAY"), Tz::Asia_Qyzylorda},
{IsoCodes::subdivisionCodeToKey("KZ-KAR"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-KUS"), Tz::Asia_Qostanay},
{IsoCodes::subdivisionCodeToKey("KZ-KZY"), Tz::Asia_Qyzylorda},
{IsoCodes::subdivisionCodeToKey("KZ-MAN"), Tz::Asia_Aqtau},
{IsoCodes::subdivisionCodeToKey("KZ-PAV"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-SEV"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-SHY"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-VOS"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-YUZ"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("KZ-ZAP"), Tz::Asia_Oral},
{IsoCodes::subdivisionCodeToKey("KZ-ZHA"), Tz::Asia_Almaty},
{IsoCodes::subdivisionCodeToKey("MA-01"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-02"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-03"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-04"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-05"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-06"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-07"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-08"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-09"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-10"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-10"), Tz::Africa_El_Aaiun},
{IsoCodes::subdivisionCodeToKey("MA-11"), Tz::Africa_El_Aaiun},
{IsoCodes::subdivisionCodeToKey("MA-11"), Tz::Africa_Casablanca},
{IsoCodes::subdivisionCodeToKey("MA-12"), Tz::Africa_El_Aaiun},
{IsoCodes::subdivisionCodeToKey("MH-L"), Tz::Pacific_Majuro},
{IsoCodes::subdivisionCodeToKey("MH-L"), Tz::Pacific_Kwajalein},
{IsoCodes::subdivisionCodeToKey("MH-T"), Tz::Pacific_Majuro},
{IsoCodes::subdivisionCodeToKey("MN-035"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-037"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-039"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-041"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-043"), Tz::Asia_Hovd},
{IsoCodes::subdivisionCodeToKey("MN-046"), Tz::Asia_Hovd},
{IsoCodes::subdivisionCodeToKey("MN-047"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-049"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-051"), Tz::Asia_Choibalsan},
{IsoCodes::subdivisionCodeToKey("MN-053"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-055"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-057"), Tz::Asia_Hovd},
{IsoCodes::subdivisionCodeToKey("MN-059"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-061"), Tz::Asia_Choibalsan},
{IsoCodes::subdivisionCodeToKey("MN-063"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-064"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-065"), Tz::Asia_Hovd},
{IsoCodes::subdivisionCodeToKey("MN-067"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-069"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-071"), Tz::Asia_Hovd},
{IsoCodes::subdivisionCodeToKey("MN-073"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MN-1"), Tz::Asia_Ulaanbaatar},
{IsoCodes::subdivisionCodeToKey("MX-AGU"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-BCN"), Tz::America_Tijuana},
{IsoCodes::subdivisionCodeToKey("MX-BCS"), Tz::America_Mazatlan},
{IsoCodes::subdivisionCodeToKey("MX-CAM"), Tz::America_Merida},
{IsoCodes::subdivisionCodeToKey("MX-CHH"), Tz::America_Chihuahua},
{IsoCodes::subdivisionCodeToKey("MX-CHH"), Tz::America_Ojinaga},
{IsoCodes::subdivisionCodeToKey("MX-CHP"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-CMX"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-COA"), Tz::America_Monterrey},
{IsoCodes::subdivisionCodeToKey("MX-COA"), Tz::America_Matamoros},
{IsoCodes::subdivisionCodeToKey("MX-COL"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-DUR"), Tz::America_Monterrey},
{IsoCodes::subdivisionCodeToKey("MX-GRO"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-GUA"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-HID"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-JAL"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-MEX"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-MIC"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-MOR"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-NAY"), Tz::America_Mazatlan},
{IsoCodes::subdivisionCodeToKey("MX-NAY"), Tz::America_Bahia_Banderas},
{IsoCodes::subdivisionCodeToKey("MX-NLE"), Tz::America_Monterrey},
{IsoCodes::subdivisionCodeToKey("MX-NLE"), Tz::America_Matamoros},
{IsoCodes::subdivisionCodeToKey("MX-OAX"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-PUE"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-QUE"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-ROO"), Tz::America_Cancun},
{IsoCodes::subdivisionCodeToKey("MX-SIN"), Tz::America_Mazatlan},
{IsoCodes::subdivisionCodeToKey("MX-SLP"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-SON"), Tz::America_Hermosillo},
{IsoCodes::subdivisionCodeToKey("MX-TAB"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-TAM"), Tz::America_Monterrey},
{IsoCodes::subdivisionCodeToKey("MX-TAM"), Tz::America_Matamoros},
{IsoCodes::subdivisionCodeToKey("MX-TLA"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-VER"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("MX-YUC"), Tz::America_Merida},
{IsoCodes::subdivisionCodeToKey("MX-ZAC"), Tz::America_Mexico_City},
{IsoCodes::subdivisionCodeToKey("NL-AW"), Tz::America_Aruba},
{IsoCodes::subdivisionCodeToKey("NL-CW"), Tz::America_Curacao},
{IsoCodes::subdivisionCodeToKey("NL-DR"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-FL"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-FR"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-GE"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-GR"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-LI"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-NB"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-NH"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-OV"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-SX"), Tz::America_Lower_Princes},
{IsoCodes::subdivisionCodeToKey("NL-UT"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-ZE"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NL-ZH"), Tz::Europe_Amsterdam},
{IsoCodes::subdivisionCodeToKey("NZ-AUK"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-BOP"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-CAN"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-CIT"), Tz::Pacific_Chatham},
{IsoCodes::subdivisionCodeToKey("NZ-GIS"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-HKB"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-MBH"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-MWT"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-NSN"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-NTL"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-OTA"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-STL"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-TAS"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-TKI"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-WGN"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-WKO"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("NZ-WTC"), Tz::Pacific_Auckland},
{IsoCodes::subdivisionCodeToKey("PG-CPK"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-CPM"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-EBR"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-EHG"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-EPW"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-ESW"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-GPK"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-HLA"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-JWK"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-MBA"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-MPL"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-MPM"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-MRL"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-NCD"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-NIK"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-NIK"), Tz::Pacific_Bougainville},
{IsoCodes::subdivisionCodeToKey("PG-NPP"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-NSB"), Tz::Pacific_Bougainville},
{IsoCodes::subdivisionCodeToKey("PG-SAN"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-SHM"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-WBK"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-WHM"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PG-WPD"), Tz::Pacific_Port_Moresby},
{IsoCodes::subdivisionCodeToKey("PS-BTH"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-BTH"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-DEB"), Tz::Asia_Gaza},
{IsoCodes::subdivisionCodeToKey("PS-GZA"), Tz::Asia_Gaza},
{IsoCodes::subdivisionCodeToKey("PS-HBN"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-HBN"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-JEM"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-JEM"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-JEN"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-JEN"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-JRH"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-JRH"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-KYS"), Tz::Asia_Gaza},
{IsoCodes::subdivisionCodeToKey("PS-NBS"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-NBS"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-NGZ"), Tz::Asia_Gaza},
{IsoCodes::subdivisionCodeToKey("PS-QQA"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-QQA"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-RBH"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-RBH"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-RFH"), Tz::Asia_Gaza},
{IsoCodes::subdivisionCodeToKey("PS-SLT"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-SLT"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-TBS"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-TBS"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PS-TKM"), Tz::Asia_Hebron},
{IsoCodes::subdivisionCodeToKey("PS-TKM"), Tz::Asia_Jerusalem},
{IsoCodes::subdivisionCodeToKey("PT-01"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-02"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-03"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-04"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-05"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-06"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-07"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-08"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-09"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-10"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-11"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-12"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-13"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-14"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-15"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-16"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-17"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-18"), Tz::Europe_Lisbon},
{IsoCodes::subdivisionCodeToKey("PT-20"), Tz::Atlantic_Azores},
{IsoCodes::subdivisionCodeToKey("PT-30"), Tz::Atlantic_Madeira},
{IsoCodes::subdivisionCodeToKey("RU-AD"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-AL"), Tz::Asia_Barnaul},
{IsoCodes::subdivisionCodeToKey("RU-ALT"), Tz::Asia_Barnaul},
{IsoCodes::subdivisionCodeToKey("RU-AMU"), Tz::Asia_Yakutsk},
{IsoCodes::subdivisionCodeToKey("RU-ARK"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-AST"), Tz::Europe_Astrakhan},
{IsoCodes::subdivisionCodeToKey("RU-BA"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-BEL"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-BRY"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-BU"), Tz::Asia_Irkutsk},
{IsoCodes::subdivisionCodeToKey("RU-CE"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-CHE"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-CHU"), Tz::Asia_Anadyr},
{IsoCodes::subdivisionCodeToKey("RU-CU"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-DA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-EAO"), Tz::Asia_Krasnoyarsk},
{IsoCodes::subdivisionCodeToKey("RU-IN"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-IRK"), Tz::Asia_Irkutsk},
{IsoCodes::subdivisionCodeToKey("RU-IVA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KAM"), Tz::Asia_Kamchatka},
{IsoCodes::subdivisionCodeToKey("RU-KB"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KC"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KDA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KEM"), Tz::Asia_Novokuznetsk},
{IsoCodes::subdivisionCodeToKey("RU-KGD"), Tz::Europe_Kaliningrad},
{IsoCodes::subdivisionCodeToKey("RU-KGN"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-KHA"), Tz::Asia_Vladivostok},
{IsoCodes::subdivisionCodeToKey("RU-KHM"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-KIR"), Tz::Europe_Kirov},
{IsoCodes::subdivisionCodeToKey("RU-KK"), Tz::Asia_Krasnoyarsk},
{IsoCodes::subdivisionCodeToKey("RU-KL"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KLU"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KO"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KOS"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KR"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KRS"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-KYA"), Tz::Asia_Krasnoyarsk},
{IsoCodes::subdivisionCodeToKey("RU-LEN"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-LIP"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-MAG"), Tz::Asia_Magadan},
{IsoCodes::subdivisionCodeToKey("RU-ME"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-MO"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-MOS"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-MOW"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-MUR"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-NEN"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-NGR"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-NIZ"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-NVS"), Tz::Asia_Novosibirsk},
{IsoCodes::subdivisionCodeToKey("RU-OMS"), Tz::Asia_Omsk},
{IsoCodes::subdivisionCodeToKey("RU-ORE"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-ORL"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-PER"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-PNZ"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-PRI"), Tz::Asia_Vladivostok},
{IsoCodes::subdivisionCodeToKey("RU-PSK"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-ROS"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-RYA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-SA"), Tz::Asia_Yakutsk},
{IsoCodes::subdivisionCodeToKey("RU-SA"), Tz::Asia_Srednekolymsk},
{IsoCodes::subdivisionCodeToKey("RU-SA"), Tz::Asia_Vladivostok},
{IsoCodes::subdivisionCodeToKey("RU-SA"), Tz::Asia_Khandyga},
{IsoCodes::subdivisionCodeToKey("RU-SA"), Tz::Asia_Ust_Nera},
{IsoCodes::subdivisionCodeToKey("RU-SAK"), Tz::Asia_Sakhalin},
{IsoCodes::subdivisionCodeToKey("RU-SAK"), Tz::Asia_Ust_Nera},
{IsoCodes::subdivisionCodeToKey("RU-SAK"), Tz::Asia_Srednekolymsk},
{IsoCodes::subdivisionCodeToKey("RU-SAM"), Tz::Europe_Samara},
{IsoCodes::subdivisionCodeToKey("RU-SAR"), Tz::Europe_Saratov},
{IsoCodes::subdivisionCodeToKey("RU-SE"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-SMO"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-SPE"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-STA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-SVE"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-TA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-TAM"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-TOM"), Tz::Asia_Tomsk},
{IsoCodes::subdivisionCodeToKey("RU-TUL"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-TVE"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-TY"), Tz::Asia_Krasnoyarsk},
{IsoCodes::subdivisionCodeToKey("RU-TYU"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-UD"), Tz::Europe_Samara},
{IsoCodes::subdivisionCodeToKey("RU-ULY"), Tz::Europe_Ulyanovsk},
{IsoCodes::subdivisionCodeToKey("RU-VGG"), Tz::Europe_Volgograd},
{IsoCodes::subdivisionCodeToKey("RU-VLA"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-VLG"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-VOR"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-YAN"), Tz::Asia_Yekaterinburg},
{IsoCodes::subdivisionCodeToKey("RU-YAR"), Tz::Europe_Moscow},
{IsoCodes::subdivisionCodeToKey("RU-YEV"), Tz::Asia_Vladivostok},
{IsoCodes::subdivisionCodeToKey("RU-ZAB"), Tz::Asia_Chita},
{IsoCodes::subdivisionCodeToKey("SD-DC"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-DE"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-DN"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-DS"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-DW"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-GD"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-GK"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-GK"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SD-GZ"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-KA"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-KH"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-KN"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-KS"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-NB"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-NO"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-NR"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-NW"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-RS"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SD-SI"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SS-BN"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-BW"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-BW"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("SS-EC"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-EE"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-EW"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-JG"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-LK"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-NU"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-UY"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-WR"), Tz::Africa_Juba},
{IsoCodes::subdivisionCodeToKey("SS-WR"), Tz::Africa_Khartoum},
{IsoCodes::subdivisionCodeToKey("UA-05"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-07"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-09"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-12"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-14"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-18"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-21"), Tz::Europe_Uzhgorod},
{IsoCodes::subdivisionCodeToKey("UA-23"), Tz::Europe_Zaporozhye},
{IsoCodes::subdivisionCodeToKey("UA-26"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-30"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-32"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-35"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-40"), Tz::Europe_Simferopol},
{IsoCodes::subdivisionCodeToKey("UA-43"), Tz::Europe_Simferopol},
{IsoCodes::subdivisionCodeToKey("UA-46"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-48"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-51"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-53"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-56"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-59"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-61"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-63"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-65"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-68"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-71"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-74"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UA-77"), Tz::Europe_Kiev},
{IsoCodes::subdivisionCodeToKey("UM-67"), Tz::Pacific_Honolulu},
{IsoCodes::subdivisionCodeToKey("UM-71"), Tz::Pacific_Midway},
{IsoCodes::subdivisionCodeToKey("UM-76"), Tz::America_Port_au_Prince},
{IsoCodes::subdivisionCodeToKey("UM-79"), Tz::Pacific_Wake},
// UM-81
// UM-84
// UM-86
// UM-89
// UM-95
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Anchorage},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Nome},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Sitka},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Adak},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Juneau},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Yakutat},
{IsoCodes::subdivisionCodeToKey("US-AK"), Tz::America_Metlakatla},
{IsoCodes::subdivisionCodeToKey("US-AL"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-AR"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-AS"), Tz::Pacific_Pago_Pago},
{IsoCodes::subdivisionCodeToKey("US-AZ"), Tz::America_Phoenix},
{IsoCodes::subdivisionCodeToKey("US-AZ"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-CA"), Tz::America_Los_Angeles},
{IsoCodes::subdivisionCodeToKey("US-CO"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-CT"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-DC"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-DE"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-FL"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-FL"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-GA"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-GU"), Tz::Pacific_Guam},
{IsoCodes::subdivisionCodeToKey("US-HI"), Tz::Pacific_Honolulu},
{IsoCodes::subdivisionCodeToKey("US-HI"), Tz::Pacific_Midway},
{IsoCodes::subdivisionCodeToKey("US-IA"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-ID"), Tz::America_Boise},
{IsoCodes::subdivisionCodeToKey("US-ID"), Tz::America_Los_Angeles},
{IsoCodes::subdivisionCodeToKey("US-IL"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Indianapolis},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Vincennes},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Kentucky_Louisville},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Winamac},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Tell_City},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Petersburg},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Knox},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Marengo},
{IsoCodes::subdivisionCodeToKey("US-IN"), Tz::America_Indiana_Vevay},
{IsoCodes::subdivisionCodeToKey("US-KS"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-KY"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-KY"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-KY"), Tz::America_Kentucky_Monticello},
{IsoCodes::subdivisionCodeToKey("US-KY"), Tz::America_Kentucky_Louisville},
{IsoCodes::subdivisionCodeToKey("US-LA"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-MA"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-MD"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-ME"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-MI"), Tz::America_Detroit},
{IsoCodes::subdivisionCodeToKey("US-MI"), Tz::America_Menominee},
{IsoCodes::subdivisionCodeToKey("US-MN"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-MO"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-MP"), Tz::Pacific_Saipan},
{IsoCodes::subdivisionCodeToKey("US-MS"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-MT"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-NC"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-ND"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-ND"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-ND"), Tz::America_North_Dakota_New_Salem},
{IsoCodes::subdivisionCodeToKey("US-ND"), Tz::America_North_Dakota_Beulah},
{IsoCodes::subdivisionCodeToKey("US-ND"), Tz::America_North_Dakota_Center},
{IsoCodes::subdivisionCodeToKey("US-NE"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-NE"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-NH"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-NJ"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-NM"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-NV"), Tz::America_Los_Angeles},
{IsoCodes::subdivisionCodeToKey("US-NY"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-OH"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-OK"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-OR"), Tz::America_Los_Angeles},
{IsoCodes::subdivisionCodeToKey("US-OR"), Tz::America_Boise},
{IsoCodes::subdivisionCodeToKey("US-PA"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-PR"), Tz::America_Puerto_Rico},
{IsoCodes::subdivisionCodeToKey("US-RI"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-SC"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-SD"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-SD"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-TN"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-TN"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-TX"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-UM"), Tz::Pacific_Honolulu},
{IsoCodes::subdivisionCodeToKey("US-UM"), Tz::Pacific_Midway},
{IsoCodes::subdivisionCodeToKey("US-UM"), Tz::Pacific_Wake},
{IsoCodes::subdivisionCodeToKey("US-UM"), Tz::America_Port_au_Prince},
{IsoCodes::subdivisionCodeToKey("US-UT"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("US-VA"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-VI"), Tz::America_St_Thomas},
{IsoCodes::subdivisionCodeToKey("US-VT"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-WA"), Tz::America_Los_Angeles},
{IsoCodes::subdivisionCodeToKey("US-WI"), Tz::America_Chicago},
{IsoCodes::subdivisionCodeToKey("US-WV"), Tz::America_New_York},
{IsoCodes::subdivisionCodeToKey("US-WY"), Tz::America_Denver},
{IsoCodes::subdivisionCodeToKey("UZ-AN"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-BU"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-FA"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-JI"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-NG"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-NW"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-QA"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-QR"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-SA"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-SI"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-SU"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("UZ-TK"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-TO"), Tz::Asia_Tashkent},
{IsoCodes::subdivisionCodeToKey("UZ-XO"), Tz::Asia_Samarkand},
{IsoCodes::subdivisionCodeToKey("VN-01"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-02"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-03"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-04"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-05"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-06"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-07"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-09"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-13"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-14"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-18"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-20"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-21"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-22"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-23"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-24"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-25"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-26"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-27"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-28"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-29"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-30"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-31"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-32"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-33"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-34"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-35"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-36"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-37"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-39"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-40"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-41"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-43"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-44"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-45"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-46"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-47"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-49"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-50"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-51"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-52"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-53"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-54"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-55"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-56"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-57"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-58"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-59"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-61"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-63"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-66"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-67"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-68"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-69"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-70"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-71"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-72"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-73"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-CT"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-DN"), Tz::Asia_Ho_Chi_Minh},
{IsoCodes::subdivisionCodeToKey("VN-HN"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-HP"), Tz::Asia_Bangkok},
{IsoCodes::subdivisionCodeToKey("VN-SG"), Tz::Asia_Ho_Chi_Minh},
};
@@ -0,0 +1,434 @@
/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
static constexpr const char timezone_name_table[] =
"Africa/Abidjan\0"
"Africa/Accra\0"
"Africa/Addis_Ababa\0"
"Africa/Algiers\0"
"Africa/Asmara\0"
"Africa/Bamako\0"
"Africa/Bangui\0"
"Africa/Banjul\0"
"Africa/Bissau\0"
"Africa/Blantyre\0"
"Africa/Brazzaville\0"
"Africa/Bujumbura\0"
"Africa/Cairo\0"
"Africa/Casablanca\0"
"Africa/Ceuta\0"
"Africa/Conakry\0"
"Africa/Dakar\0"
"Africa/Dar_es_Salaam\0"
"Africa/Djibouti\0"
"Africa/Douala\0"
"Africa/El_Aaiun\0"
"Africa/Freetown\0"
"Africa/Gaborone\0"
"Africa/Harare\0"
"Africa/Johannesburg\0"
"Africa/Juba\0"
"Africa/Kampala\0"
"Africa/Khartoum\0"
"Africa/Kigali\0"
"Africa/Kinshasa\0"
"Africa/Lagos\0"
"Africa/Libreville\0"
"Africa/Lome\0"
"Africa/Luanda\0"
"Africa/Lubumbashi\0"
"Africa/Lusaka\0"
"Africa/Malabo\0"
"Africa/Maputo\0"
"Africa/Maseru\0"
"Africa/Mbabane\0"
"Africa/Mogadishu\0"
"Africa/Monrovia\0"
"Africa/Nairobi\0"
"Africa/Ndjamena\0"
"Africa/Niamey\0"
"Africa/Nouakchott\0"
"Africa/Ouagadougou\0"
"Africa/Porto-Novo\0"
"Africa/Sao_Tome\0"
"Africa/Tripoli\0"
"Africa/Tunis\0"
"Africa/Windhoek\0"
"America/Adak\0"
"America/Anchorage\0"
"America/Anguilla\0"
"America/Antigua\0"
"America/Araguaina\0"
"America/Argentina/Buenos_Aires\0"
"America/Argentina/Catamarca\0"
"America/Argentina/Cordoba\0"
"America/Argentina/Jujuy\0"
"America/Argentina/La_Rioja\0"
"America/Argentina/Mendoza\0"
"America/Argentina/Rio_Gallegos\0"
"America/Argentina/Salta\0"
"America/Argentina/San_Juan\0"
"America/Argentina/San_Luis\0"
"America/Argentina/Tucuman\0"
"America/Argentina/Ushuaia\0"
"America/Aruba\0"
"America/Asuncion\0"
"America/Atikokan\0"
"America/Bahia\0"
"America/Bahia_Banderas\0"
"America/Barbados\0"
"America/Belem\0"
"America/Belize\0"
"America/Blanc-Sablon\0"
"America/Boa_Vista\0"
"America/Bogota\0"
"America/Boise\0"
"America/Cambridge_Bay\0"
"America/Campo_Grande\0"
"America/Cancun\0"
"America/Caracas\0"
"America/Cayenne\0"
"America/Cayman\0"
"America/Chicago\0"
"America/Chihuahua\0"
"America/Costa_Rica\0"
"America/Creston\0"
"America/Cuiaba\0"
"America/Curacao\0"
"America/Danmarkshavn\0"
"America/Dawson\0"
"America/Dawson_Creek\0"
"America/Denver\0"
"America/Detroit\0"
"America/Dominica\0"
"America/Edmonton\0"
"America/Eirunepe\0"
"America/El_Salvador\0"
"America/Fort_Nelson\0"
"America/Fortaleza\0"
"America/Glace_Bay\0"
"America/Goose_Bay\0"
"America/Grand_Turk\0"
"America/Grenada\0"
"America/Guadeloupe\0"
"America/Guatemala\0"
"America/Guayaquil\0"
"America/Guyana\0"
"America/Halifax\0"
"America/Havana\0"
"America/Hermosillo\0"
"America/Indiana/Indianapolis\0"
"America/Indiana/Knox\0"
"America/Indiana/Marengo\0"
"America/Indiana/Petersburg\0"
"America/Indiana/Tell_City\0"
"America/Indiana/Vevay\0"
"America/Indiana/Vincennes\0"
"America/Indiana/Winamac\0"
"America/Inuvik\0"
"America/Iqaluit\0"
"America/Jamaica\0"
"America/Juneau\0"
"America/Kentucky/Louisville\0"
"America/Kentucky/Monticello\0"
"America/Kralendijk\0"
"America/La_Paz\0"
"America/Lima\0"
"America/Los_Angeles\0"
"America/Lower_Princes\0"
"America/Maceio\0"
"America/Managua\0"
"America/Manaus\0"
"America/Marigot\0"
"America/Martinique\0"
"America/Matamoros\0"
"America/Mazatlan\0"
"America/Menominee\0"
"America/Merida\0"
"America/Metlakatla\0"
"America/Mexico_City\0"
"America/Miquelon\0"
"America/Moncton\0"
"America/Monterrey\0"
"America/Montevideo\0"
"America/Montserrat\0"
"America/Nassau\0"
"America/New_York\0"
"America/Nipigon\0"
"America/Nome\0"
"America/Noronha\0"
"America/North_Dakota/Beulah\0"
"America/North_Dakota/Center\0"
"America/North_Dakota/New_Salem\0"
"America/Nuuk\0"
"America/Ojinaga\0"
"America/Panama\0"
"America/Pangnirtung\0"
"America/Paramaribo\0"
"America/Phoenix\0"
"America/Port-au-Prince\0"
"America/Port_of_Spain\0"
"America/Porto_Velho\0"
"America/Puerto_Rico\0"
"America/Punta_Arenas\0"
"America/Rainy_River\0"
"America/Rankin_Inlet\0"
"America/Recife\0"
"America/Regina\0"
"America/Resolute\0"
"America/Rio_Branco\0"
"America/Santarem\0"
"America/Santiago\0"
"America/Santo_Domingo\0"
"America/Sao_Paulo\0"
"America/Scoresbysund\0"
"America/Sitka\0"
"America/St_Barthelemy\0"
"America/St_Johns\0"
"America/St_Kitts\0"
"America/St_Lucia\0"
"America/St_Thomas\0"
"America/St_Vincent\0"
"America/Swift_Current\0"
"America/Tegucigalpa\0"
"America/Thule\0"
"America/Thunder_Bay\0"
"America/Tijuana\0"
"America/Toronto\0"
"America/Tortola\0"
"America/Vancouver\0"
"America/Whitehorse\0"
"America/Winnipeg\0"
"America/Yakutat\0"
"America/Yellowknife\0"
"Antarctica/Casey\0"
"Antarctica/Davis\0"
"Antarctica/DumontDUrville\0"
"Antarctica/Macquarie\0"
"Antarctica/Mawson\0"
"Antarctica/McMurdo\0"
"Antarctica/Palmer\0"
"Antarctica/Rothera\0"
"Antarctica/Syowa\0"
"Antarctica/Troll\0"
"Antarctica/Vostok\0"
"Arctic/Longyearbyen\0"
"Asia/Aden\0"
"Asia/Almaty\0"
"Asia/Amman\0"
"Asia/Anadyr\0"
"Asia/Aqtau\0"
"Asia/Aqtobe\0"
"Asia/Ashgabat\0"
"Asia/Atyrau\0"
"Asia/Baghdad\0"
"Asia/Bahrain\0"
"Asia/Baku\0"
"Asia/Bangkok\0"
"Asia/Barnaul\0"
"Asia/Beirut\0"
"Asia/Bishkek\0"
"Asia/Brunei\0"
"Asia/Chita\0"
"Asia/Choibalsan\0"
"Asia/Colombo\0"
"Asia/Damascus\0"
"Asia/Dhaka\0"
"Asia/Dili\0"
"Asia/Dubai\0"
"Asia/Dushanbe\0"
"Asia/Famagusta\0"
"Asia/Gaza\0"
"Asia/Hebron\0"
"Asia/Ho_Chi_Minh\0"
"Asia/Hong_Kong\0"
"Asia/Hovd\0"
"Asia/Irkutsk\0"
"Asia/Jakarta\0"
"Asia/Jayapura\0"
"Asia/Jerusalem\0"
"Asia/Kabul\0"
"Asia/Kamchatka\0"
"Asia/Karachi\0"
"Asia/Kathmandu\0"
"Asia/Khandyga\0"
"Asia/Kolkata\0"
"Asia/Krasnoyarsk\0"
"Asia/Kuala_Lumpur\0"
"Asia/Kuching\0"
"Asia/Kuwait\0"
"Asia/Macau\0"
"Asia/Magadan\0"
"Asia/Makassar\0"
"Asia/Manila\0"
"Asia/Muscat\0"
"Asia/Nicosia\0"
"Asia/Novokuznetsk\0"
"Asia/Novosibirsk\0"
"Asia/Omsk\0"
"Asia/Oral\0"
"Asia/Phnom_Penh\0"
"Asia/Pontianak\0"
"Asia/Pyongyang\0"
"Asia/Qatar\0"
"Asia/Qostanay\0"
"Asia/Qyzylorda\0"
"Asia/Riyadh\0"
"Asia/Sakhalin\0"
"Asia/Samarkand\0"
"Asia/Seoul\0"
"Asia/Shanghai\0"
"Asia/Singapore\0"
"Asia/Srednekolymsk\0"
"Asia/Taipei\0"
"Asia/Tashkent\0"
"Asia/Tbilisi\0"
"Asia/Tehran\0"
"Asia/Thimphu\0"
"Asia/Tokyo\0"
"Asia/Tomsk\0"
"Asia/Ulaanbaatar\0"
"Asia/Urumqi\0"
"Asia/Ust-Nera\0"
"Asia/Vientiane\0"
"Asia/Vladivostok\0"
"Asia/Yakutsk\0"
"Asia/Yangon\0"
"Asia/Yekaterinburg\0"
"Asia/Yerevan\0"
"Atlantic/Azores\0"
"Atlantic/Bermuda\0"
"Atlantic/Canary\0"
"Atlantic/Cape_Verde\0"
"Atlantic/Faroe\0"
"Atlantic/Madeira\0"
"Atlantic/Reykjavik\0"
"Atlantic/South_Georgia\0"
"Atlantic/St_Helena\0"
"Atlantic/Stanley\0"
"Australia/Adelaide\0"
"Australia/Brisbane\0"
"Australia/Broken_Hill\0"
"Australia/Currie\0"
"Australia/Darwin\0"
"Australia/Eucla\0"
"Australia/Hobart\0"
"Australia/Lindeman\0"
"Australia/Lord_Howe\0"
"Australia/Melbourne\0"
"Australia/Perth\0"
"Australia/Sydney\0"
"Etc/UTC\0"
"Europe/Amsterdam\0"
"Europe/Andorra\0"
"Europe/Astrakhan\0"
"Europe/Athens\0"
"Europe/Belgrade\0"
"Europe/Berlin\0"
"Europe/Bratislava\0"
"Europe/Brussels\0"
"Europe/Bucharest\0"
"Europe/Budapest\0"
"Europe/Busingen\0"
"Europe/Chisinau\0"
"Europe/Copenhagen\0"
"Europe/Dublin\0"
"Europe/Gibraltar\0"
"Europe/Guernsey\0"
"Europe/Helsinki\0"
"Europe/Isle_of_Man\0"
"Europe/Istanbul\0"
"Europe/Jersey\0"
"Europe/Kaliningrad\0"
"Europe/Kiev\0"
"Europe/Kirov\0"
"Europe/Lisbon\0"
"Europe/Ljubljana\0"
"Europe/London\0"
"Europe/Luxembourg\0"
"Europe/Madrid\0"
"Europe/Malta\0"
"Europe/Mariehamn\0"
"Europe/Minsk\0"
"Europe/Monaco\0"
"Europe/Moscow\0"
"Europe/Oslo\0"
"Europe/Paris\0"
"Europe/Podgorica\0"
"Europe/Prague\0"
"Europe/Riga\0"
"Europe/Rome\0"
"Europe/Samara\0"
"Europe/San_Marino\0"
"Europe/Sarajevo\0"
"Europe/Saratov\0"
"Europe/Simferopol\0"
"Europe/Skopje\0"
"Europe/Sofia\0"
"Europe/Stockholm\0"
"Europe/Tallinn\0"
"Europe/Tirane\0"
"Europe/Ulyanovsk\0"
"Europe/Uzhgorod\0"
"Europe/Vaduz\0"
"Europe/Vatican\0"
"Europe/Vienna\0"
"Europe/Vilnius\0"
"Europe/Volgograd\0"
"Europe/Warsaw\0"
"Europe/Zagreb\0"
"Europe/Zaporozhye\0"
"Europe/Zurich\0"
"Indian/Antananarivo\0"
"Indian/Chagos\0"
"Indian/Christmas\0"
"Indian/Cocos\0"
"Indian/Comoro\0"
"Indian/Kerguelen\0"
"Indian/Mahe\0"
"Indian/Maldives\0"
"Indian/Mauritius\0"
"Indian/Mayotte\0"
"Indian/Reunion\0"
"Pacific/Apia\0"
"Pacific/Auckland\0"
"Pacific/Bougainville\0"
"Pacific/Chatham\0"
"Pacific/Chuuk\0"
"Pacific/Easter\0"
"Pacific/Efate\0"
"Pacific/Enderbury\0"
"Pacific/Fakaofo\0"
"Pacific/Fiji\0"
"Pacific/Funafuti\0"
"Pacific/Galapagos\0"
"Pacific/Gambier\0"
"Pacific/Guadalcanal\0"
"Pacific/Guam\0"
"Pacific/Honolulu\0"
"Pacific/Kiritimati\0"
"Pacific/Kosrae\0"
"Pacific/Kwajalein\0"
"Pacific/Majuro\0"
"Pacific/Marquesas\0"
"Pacific/Midway\0"
"Pacific/Nauru\0"
"Pacific/Niue\0"
"Pacific/Norfolk\0"
"Pacific/Noumea\0"
"Pacific/Pago_Pago\0"
"Pacific/Palau\0"
"Pacific/Pitcairn\0"
"Pacific/Pohnpei\0"
"Pacific/Port_Moresby\0"
"Pacific/Rarotonga\0"
"Pacific/Saipan\0"
"Pacific/Tahiti\0"
"Pacific/Tarawa\0"
"Pacific/Tongatapu\0"
"Pacific/Wake\0"
"Pacific/Wallis\0";
@@ -0,0 +1,443 @@
/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#ifndef TIMEZONE_NAMES_P_H
#define TIMEZONE_NAMES_P_H
#include <cstdint>
enum Tz : uint16_t {
Africa_Abidjan = 0,
Africa_Accra = 15,
Africa_Addis_Ababa = 28,
Africa_Algiers = 47,
Africa_Asmara = 62,
Africa_Bamako = 76,
Africa_Bangui = 90,
Africa_Banjul = 104,
Africa_Bissau = 118,
Africa_Blantyre = 132,
Africa_Brazzaville = 148,
Africa_Bujumbura = 167,
Africa_Cairo = 184,
Africa_Casablanca = 197,
Africa_Ceuta = 215,
Africa_Conakry = 228,
Africa_Dakar = 243,
Africa_Dar_es_Salaam = 256,
Africa_Djibouti = 277,
Africa_Douala = 293,
Africa_El_Aaiun = 307,
Africa_Freetown = 323,
Africa_Gaborone = 339,
Africa_Harare = 355,
Africa_Johannesburg = 369,
Africa_Juba = 389,
Africa_Kampala = 401,
Africa_Khartoum = 416,
Africa_Kigali = 432,
Africa_Kinshasa = 446,
Africa_Lagos = 462,
Africa_Libreville = 475,
Africa_Lome = 493,
Africa_Luanda = 505,
Africa_Lubumbashi = 519,
Africa_Lusaka = 537,
Africa_Malabo = 551,
Africa_Maputo = 565,
Africa_Maseru = 579,
Africa_Mbabane = 593,
Africa_Mogadishu = 608,
Africa_Monrovia = 625,
Africa_Nairobi = 641,
Africa_Ndjamena = 656,
Africa_Niamey = 672,
Africa_Nouakchott = 686,
Africa_Ouagadougou = 704,
Africa_Porto_Novo = 723,
Africa_Sao_Tome = 741,
Africa_Tripoli = 757,
Africa_Tunis = 772,
Africa_Windhoek = 785,
America_Adak = 801,
America_Anchorage = 814,
America_Anguilla = 832,
America_Antigua = 849,
America_Araguaina = 865,
America_Argentina_Buenos_Aires = 883,
America_Argentina_Catamarca = 914,
America_Argentina_Cordoba = 942,
America_Argentina_Jujuy = 968,
America_Argentina_La_Rioja = 992,
America_Argentina_Mendoza = 1019,
America_Argentina_Rio_Gallegos = 1045,
America_Argentina_Salta = 1076,
America_Argentina_San_Juan = 1100,
America_Argentina_San_Luis = 1127,
America_Argentina_Tucuman = 1154,
America_Argentina_Ushuaia = 1180,
America_Aruba = 1206,
America_Asuncion = 1220,
America_Atikokan = 1237,
America_Bahia = 1254,
America_Bahia_Banderas = 1268,
America_Barbados = 1291,
America_Belem = 1308,
America_Belize = 1322,
America_Blanc_Sablon = 1337,
America_Boa_Vista = 1358,
America_Bogota = 1376,
America_Boise = 1391,
America_Cambridge_Bay = 1405,
America_Campo_Grande = 1427,
America_Cancun = 1448,
America_Caracas = 1463,
America_Cayenne = 1479,
America_Cayman = 1495,
America_Chicago = 1510,
America_Chihuahua = 1526,
America_Costa_Rica = 1544,
America_Creston = 1563,
America_Cuiaba = 1579,
America_Curacao = 1594,
America_Danmarkshavn = 1610,
America_Dawson = 1631,
America_Dawson_Creek = 1646,
America_Denver = 1667,
America_Detroit = 1682,
America_Dominica = 1698,
America_Edmonton = 1715,
America_Eirunepe = 1732,
America_El_Salvador = 1749,
America_Fort_Nelson = 1769,
America_Fortaleza = 1789,
America_Glace_Bay = 1807,
America_Goose_Bay = 1825,
America_Grand_Turk = 1843,
America_Grenada = 1862,
America_Guadeloupe = 1878,
America_Guatemala = 1897,
America_Guayaquil = 1915,
America_Guyana = 1933,
America_Halifax = 1948,
America_Havana = 1964,
America_Hermosillo = 1979,
America_Indiana_Indianapolis = 1998,
America_Indiana_Knox = 2027,
America_Indiana_Marengo = 2048,
America_Indiana_Petersburg = 2072,
America_Indiana_Tell_City = 2099,
America_Indiana_Vevay = 2125,
America_Indiana_Vincennes = 2147,
America_Indiana_Winamac = 2173,
America_Inuvik = 2197,
America_Iqaluit = 2212,
America_Jamaica = 2228,
America_Juneau = 2244,
America_Kentucky_Louisville = 2259,
America_Kentucky_Monticello = 2287,
America_Kralendijk = 2315,
America_La_Paz = 2334,
America_Lima = 2349,
America_Los_Angeles = 2362,
America_Lower_Princes = 2382,
America_Maceio = 2404,
America_Managua = 2419,
America_Manaus = 2435,
America_Marigot = 2450,
America_Martinique = 2466,
America_Matamoros = 2485,
America_Mazatlan = 2503,
America_Menominee = 2520,
America_Merida = 2538,
America_Metlakatla = 2553,
America_Mexico_City = 2572,
America_Miquelon = 2592,
America_Moncton = 2609,
America_Monterrey = 2625,
America_Montevideo = 2643,
America_Montserrat = 2662,
America_Nassau = 2681,
America_New_York = 2696,
America_Nipigon = 2713,
America_Nome = 2729,
America_Noronha = 2742,
America_North_Dakota_Beulah = 2758,
America_North_Dakota_Center = 2786,
America_North_Dakota_New_Salem = 2814,
America_Nuuk = 2845,
America_Ojinaga = 2858,
America_Panama = 2874,
America_Pangnirtung = 2889,
America_Paramaribo = 2909,
America_Phoenix = 2928,
America_Port_au_Prince = 2944,
America_Port_of_Spain = 2967,
America_Porto_Velho = 2989,
America_Puerto_Rico = 3009,
America_Punta_Arenas = 3029,
America_Rainy_River = 3050,
America_Rankin_Inlet = 3070,
America_Recife = 3091,
America_Regina = 3106,
America_Resolute = 3121,
America_Rio_Branco = 3138,
America_Santarem = 3157,
America_Santiago = 3174,
America_Santo_Domingo = 3191,
America_Sao_Paulo = 3213,
America_Scoresbysund = 3231,
America_Sitka = 3252,
America_St_Barthelemy = 3266,
America_St_Johns = 3288,
America_St_Kitts = 3305,
America_St_Lucia = 3322,
America_St_Thomas = 3339,
America_St_Vincent = 3357,
America_Swift_Current = 3376,
America_Tegucigalpa = 3398,
America_Thule = 3418,
America_Thunder_Bay = 3432,
America_Tijuana = 3452,
America_Toronto = 3468,
America_Tortola = 3484,
America_Vancouver = 3500,
America_Whitehorse = 3518,
America_Winnipeg = 3537,
America_Yakutat = 3554,
America_Yellowknife = 3570,
Antarctica_Casey = 3590,
Antarctica_Davis = 3607,
Antarctica_DumontDUrville = 3624,
Antarctica_Macquarie = 3650,
Antarctica_Mawson = 3671,
Antarctica_McMurdo = 3689,
Antarctica_Palmer = 3708,
Antarctica_Rothera = 3726,
Antarctica_Syowa = 3745,
Antarctica_Troll = 3762,
Antarctica_Vostok = 3779,
Arctic_Longyearbyen = 3797,
Asia_Aden = 3817,
Asia_Almaty = 3827,
Asia_Amman = 3839,
Asia_Anadyr = 3850,
Asia_Aqtau = 3862,
Asia_Aqtobe = 3873,
Asia_Ashgabat = 3885,
Asia_Atyrau = 3899,
Asia_Baghdad = 3911,
Asia_Bahrain = 3924,
Asia_Baku = 3937,
Asia_Bangkok = 3947,
Asia_Barnaul = 3960,
Asia_Beirut = 3973,
Asia_Bishkek = 3985,
Asia_Brunei = 3998,
Asia_Chita = 4010,
Asia_Choibalsan = 4021,
Asia_Colombo = 4037,
Asia_Damascus = 4050,
Asia_Dhaka = 4064,
Asia_Dili = 4075,
Asia_Dubai = 4085,
Asia_Dushanbe = 4096,
Asia_Famagusta = 4110,
Asia_Gaza = 4125,
Asia_Hebron = 4135,
Asia_Ho_Chi_Minh = 4147,
Asia_Hong_Kong = 4164,
Asia_Hovd = 4179,
Asia_Irkutsk = 4189,
Asia_Jakarta = 4202,
Asia_Jayapura = 4215,
Asia_Jerusalem = 4229,
Asia_Kabul = 4244,
Asia_Kamchatka = 4255,
Asia_Karachi = 4270,
Asia_Kathmandu = 4283,
Asia_Khandyga = 4298,
Asia_Kolkata = 4312,
Asia_Krasnoyarsk = 4325,
Asia_Kuala_Lumpur = 4342,
Asia_Kuching = 4360,
Asia_Kuwait = 4373,
Asia_Macau = 4385,
Asia_Magadan = 4396,
Asia_Makassar = 4409,
Asia_Manila = 4423,
Asia_Muscat = 4435,
Asia_Nicosia = 4447,
Asia_Novokuznetsk = 4460,
Asia_Novosibirsk = 4478,
Asia_Omsk = 4495,
Asia_Oral = 4505,
Asia_Phnom_Penh = 4515,
Asia_Pontianak = 4531,
Asia_Pyongyang = 4546,
Asia_Qatar = 4561,
Asia_Qostanay = 4572,
Asia_Qyzylorda = 4586,
Asia_Riyadh = 4601,
Asia_Sakhalin = 4613,
Asia_Samarkand = 4627,
Asia_Seoul = 4642,
Asia_Shanghai = 4653,
Asia_Singapore = 4667,
Asia_Srednekolymsk = 4682,
Asia_Taipei = 4701,
Asia_Tashkent = 4713,
Asia_Tbilisi = 4727,
Asia_Tehran = 4740,
Asia_Thimphu = 4752,
Asia_Tokyo = 4765,
Asia_Tomsk = 4776,
Asia_Ulaanbaatar = 4787,
Asia_Urumqi = 4804,
Asia_Ust_Nera = 4816,
Asia_Vientiane = 4830,
Asia_Vladivostok = 4845,
Asia_Yakutsk = 4862,
Asia_Yangon = 4875,
Asia_Yekaterinburg = 4887,
Asia_Yerevan = 4906,
Atlantic_Azores = 4919,
Atlantic_Bermuda = 4935,
Atlantic_Canary = 4952,
Atlantic_Cape_Verde = 4968,
Atlantic_Faroe = 4988,
Atlantic_Madeira = 5003,
Atlantic_Reykjavik = 5020,
Atlantic_South_Georgia = 5039,
Atlantic_St_Helena = 5062,
Atlantic_Stanley = 5081,
Australia_Adelaide = 5098,
Australia_Brisbane = 5117,
Australia_Broken_Hill = 5136,
Australia_Currie = 5158,
Australia_Darwin = 5175,
Australia_Eucla = 5192,
Australia_Hobart = 5208,
Australia_Lindeman = 5225,
Australia_Lord_Howe = 5244,
Australia_Melbourne = 5264,
Australia_Perth = 5284,
Australia_Sydney = 5300,
Etc_UTC = 5317,
Europe_Amsterdam = 5325,
Europe_Andorra = 5342,
Europe_Astrakhan = 5357,
Europe_Athens = 5374,
Europe_Belgrade = 5388,
Europe_Berlin = 5404,
Europe_Bratislava = 5418,
Europe_Brussels = 5436,
Europe_Bucharest = 5452,
Europe_Budapest = 5469,
Europe_Busingen = 5485,
Europe_Chisinau = 5501,
Europe_Copenhagen = 5517,
Europe_Dublin = 5535,
Europe_Gibraltar = 5549,
Europe_Guernsey = 5566,
Europe_Helsinki = 5582,
Europe_Isle_of_Man = 5598,
Europe_Istanbul = 5617,
Europe_Jersey = 5633,
Europe_Kaliningrad = 5647,
Europe_Kiev = 5666,
Europe_Kirov = 5678,
Europe_Lisbon = 5691,
Europe_Ljubljana = 5705,
Europe_London = 5722,
Europe_Luxembourg = 5736,
Europe_Madrid = 5754,
Europe_Malta = 5768,
Europe_Mariehamn = 5781,
Europe_Minsk = 5798,
Europe_Monaco = 5811,
Europe_Moscow = 5825,
Europe_Oslo = 5839,
Europe_Paris = 5851,
Europe_Podgorica = 5864,
Europe_Prague = 5881,
Europe_Riga = 5895,
Europe_Rome = 5907,
Europe_Samara = 5919,
Europe_San_Marino = 5933,
Europe_Sarajevo = 5951,
Europe_Saratov = 5967,
Europe_Simferopol = 5982,
Europe_Skopje = 6000,
Europe_Sofia = 6014,
Europe_Stockholm = 6027,
Europe_Tallinn = 6044,
Europe_Tirane = 6059,
Europe_Ulyanovsk = 6073,
Europe_Uzhgorod = 6090,
Europe_Vaduz = 6106,
Europe_Vatican = 6119,
Europe_Vienna = 6134,
Europe_Vilnius = 6148,
Europe_Volgograd = 6163,
Europe_Warsaw = 6180,
Europe_Zagreb = 6194,
Europe_Zaporozhye = 6208,
Europe_Zurich = 6226,
Indian_Antananarivo = 6240,
Indian_Chagos = 6260,
Indian_Christmas = 6274,
Indian_Cocos = 6291,
Indian_Comoro = 6304,
Indian_Kerguelen = 6318,
Indian_Mahe = 6335,
Indian_Maldives = 6347,
Indian_Mauritius = 6363,
Indian_Mayotte = 6380,
Indian_Reunion = 6395,
Pacific_Apia = 6410,
Pacific_Auckland = 6423,
Pacific_Bougainville = 6440,
Pacific_Chatham = 6461,
Pacific_Chuuk = 6477,
Pacific_Easter = 6491,
Pacific_Efate = 6506,
Pacific_Enderbury = 6520,
Pacific_Fakaofo = 6538,
Pacific_Fiji = 6554,
Pacific_Funafuti = 6567,
Pacific_Galapagos = 6584,
Pacific_Gambier = 6602,
Pacific_Guadalcanal = 6618,
Pacific_Guam = 6638,
Pacific_Honolulu = 6651,
Pacific_Kiritimati = 6668,
Pacific_Kosrae = 6687,
Pacific_Kwajalein = 6702,
Pacific_Majuro = 6720,
Pacific_Marquesas = 6735,
Pacific_Midway = 6753,
Pacific_Nauru = 6768,
Pacific_Niue = 6782,
Pacific_Norfolk = 6795,
Pacific_Noumea = 6811,
Pacific_Pago_Pago = 6826,
Pacific_Palau = 6844,
Pacific_Pitcairn = 6858,
Pacific_Pohnpei = 6875,
Pacific_Port_Moresby = 6891,
Pacific_Rarotonga = 6912,
Pacific_Saipan = 6930,
Pacific_Tahiti = 6945,
Pacific_Tarawa = 6960,
Pacific_Tongatapu = 6975,
Pacific_Wake = 6993,
Pacific_Wallis = 7006,
Undefined = 7020,
};
#endif
@@ -0,0 +1,68 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "isocodes_p.h"
#include <limits>
// "unit tests" for the low-level encoding functions
static_assert(IsoCodes::mapToUpper(QLatin1Char('a')) == 'A');
static_assert(IsoCodes::mapToAlphaNumKey(QLatin1Char('A')) == 11);
static_assert(IsoCodes::mapToAlphaNumKey('A') == IsoCodes::mapToAlphaNumKey('a'));
static_assert(IsoCodes::mapToAlphaNumKey(QLatin1Char('z')) < IsoCodes::AlphaNumKeyFactor);
static_assert(IsoCodes::mapToAlphaNumKey(QLatin1Char('0')) == 1);
static_assert(IsoCodes::mapToAlphaNumKey(QLatin1Char('9')) == 10);
static_assert(IsoCodes::AlphaNumKeyFactor * IsoCodes::AlphaNumKeyFactor * IsoCodes::AlphaNumKeyFactor < std::numeric_limits<uint16_t>::max());
static_assert(IsoCodes::mapFromAlphaNumKey(IsoCodes::mapToAlphaNumKey('0')) == '0');
static_assert(IsoCodes::mapFromAlphaNumKey(IsoCodes::mapToAlphaNumKey('9')) == '9');
static_assert(IsoCodes::mapFromAlphaNumKey(IsoCodes::mapToAlphaNumKey('a')) == 'A');
static_assert(IsoCodes::mapFromAlphaNumKey(IsoCodes::mapToAlphaNumKey('Z')) == 'Z');
static_assert(IsoCodes::alpha2CodeToKey("AZ") == 0x415a);
static_assert(IsoCodes::alpha2CodeToKey("az") == 0x415a);
static_assert(IsoCodes::alpha2CodeToKey("Az") == 0x415a);
static_assert(IsoCodes::alpha2CodeToKey("ZA") == 0x5a41);
static_assert(IsoCodes::alpha2CodeToKey("NZ") == IsoCodes::alpha2CodeToKey(u"NZ"));
static_assert(IsoCodes::alpha2CodeToKey("") == 0);
static_assert(IsoCodes::alpha2CodeToKey("12") == 0);
static_assert(IsoCodes::alpha2CodeToKey("A") == 0);
static_assert(IsoCodes::alpha2CodeToKey("ABC") == 0);
static_assert(IsoCodes::alpha2CodeToKey("A@") == 0);
static_assert(IsoCodes::alpha2CodeToKey("AA") < IsoCodes::alpha2CodeToKey("AB"));
static_assert(IsoCodes::alpha2CodeToKey("AZ") < IsoCodes::alpha2CodeToKey("BA"));
static_assert(IsoCodes::alpha3CodeToKey("ZZZ") < std::numeric_limits<uint16_t>::max());
static_assert(IsoCodes::alpha3CodeToKey("AAA") > 0);
static_assert(IsoCodes::alpha3CodeToKey("AAA") < IsoCodes::alpha3CodeToKey("AAB"));
static_assert(IsoCodes::alpha3CodeToKey("AAB") < IsoCodes::alpha3CodeToKey("BAA"));
static_assert(IsoCodes::alpha3CodeToKey("NZL") == IsoCodes::alpha3CodeToKey(u"NZL"));
static_assert(IsoCodes::alpha3CodeToKey("") == 0);
static_assert(IsoCodes::alpha3CodeToKey("AA") == 0);
static_assert(IsoCodes::alpha3CodeToKey("ABCD") == 0);
static_assert(IsoCodes::alpha3CodeToKey("AB1") == 0);
static_assert(IsoCodes::alpha3CodeToKey("A@C") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("AA-AAA") > 0);
static_assert(IsoCodes::subdivisionCodeToKey("ZZ-ZZZ") > 0);
static_assert(IsoCodes::subdivisionCodeToKey("ZZ-999") < std::numeric_limits<uint32_t>::max());
static_assert(IsoCodes::subdivisionCodeToKey("AA-A") < IsoCodes::subdivisionCodeToKey("AA-AA"));
static_assert(IsoCodes::subdivisionCodeToKey("AA-AAA") < IsoCodes::subdivisionCodeToKey("AA-AAB"));
static_assert(IsoCodes::subdivisionCodeToKey("AA-AAA") < IsoCodes::subdivisionCodeToKey("AA-AB"));
static_assert(IsoCodes::subdivisionCodeToKey("AA-AAB") < IsoCodes::subdivisionCodeToKey("AA-BAA"));
static_assert(IsoCodes::subdivisionCodeToKey("AA-AA1") < IsoCodes::subdivisionCodeToKey("AA-AAZ"));
static_assert(IsoCodes::subdivisionCodeToKey("FR-99") < IsoCodes::subdivisionCodeToKey("FR-RE"));
static_assert(IsoCodes::subdivisionCodeToKey("AB-cd1") == IsoCodes::subdivisionCodeToKey("AB-CD1"));
static_assert(IsoCodes::subdivisionCodeToKey("") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("AA-") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("12-ABC") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("AA-@") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("AB") == 0);
static_assert(IsoCodes::subdivisionCodeToKey("ABC") == 0);
@@ -0,0 +1,200 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef ISOCODES_P_H
#define ISOCODES_P_H
#include <QStringView>
#include <cstdint>
/**
* Utilities for efficient storage of ISO codes.
* The focus on constexpr here matters, as this is used for the compiled in data tables,
* anything in there has to be constexpr in order to put the data tables into the shared read-only
* data section.
*
* There's basically two formats in here:
* - upper case + digit codes of up to 3 characters are stored as a 3 digit base 37 number, which
* fits into a 16bit value
* To simplify data table handling this is done in a way that lexicographical order is retained.
* - two letter upper case codes like ISO 3166-1 alpha2: those are stored equivalent to a const char[2]
* technically the same approach as for 3 char codes could be used as well, but that doesn't give us
* any space advantage while considerably easing debugging (codes are directly readable).
*/
namespace IsoCodes
{
constexpr inline bool isAlpha(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
constexpr inline bool isAlpha(QChar c)
{
return c.row() == 0 ? isAlpha(c.cell()) : false;
}
constexpr inline bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
constexpr inline bool isDigit(QChar c)
{
return c.row() == 0 ? isDigit(c.cell()) : false;
}
constexpr inline uint8_t mapToUpper(char c)
{
return c >= 'a' ? c - 32 : c;
}
constexpr inline uint8_t mapToUpper(QChar c)
{
return mapToUpper(c.cell());
}
template<typename T>
constexpr inline uint16_t alpha2CodeToKey(T code, std::size_t size)
{
return (size == 2 && isAlpha(code[0]) && isAlpha(code[1])) ? mapToUpper(code[0]) << 8 | mapToUpper(code[1]) : 0;
}
template<std::size_t N>
constexpr inline uint16_t alpha2CodeToKey(const char (&code)[N])
{
return alpha2CodeToKey(code, N - 1);
}
constexpr inline uint16_t alpha2CodeToKey(QStringView code)
{
return alpha2CodeToKey(code, code.size());
}
constexpr inline uint8_t mapToAlphaNumKey(char c)
{
// this maps digits 0-9 to values 1-10, and letters to the numbers 11-36
uint8_t key = c;
if (key <= '9') {
return key - '/';
} else if (key >= 'a') {
key -= 32;
}
return key - 'A' + 11;
}
constexpr inline uint8_t mapToAlphaNumKey(QChar c)
{
return mapToAlphaNumKey(c.cell());
}
enum {
AlphaNumKeyFactor = 37,
};
template<typename T>
constexpr inline char mapFromAlphaNumKey(T key)
{
char c = key % IsoCodes::AlphaNumKeyFactor;
if (c > 0 && c < 11) {
return c + '/';
}
if (c >= 11) {
return c + 'A' - 11;
}
return 0;
}
template<typename T>
constexpr inline uint16_t alphaNum3CodeToKey(T code, std::size_t size)
{
if (size > 3 || size == 0) {
return 0;
}
uint16_t key = 0;
for (std::size_t i = 0; i < size; ++i) {
if (!isAlpha(code[i]) && !isDigit(code[i])) {
return 0;
}
key *= AlphaNumKeyFactor;
key += mapToAlphaNumKey(code[i]);
}
for (std::size_t i = size; i < 3; ++i) { // "left align" to retain lexicographical order
key *= AlphaNumKeyFactor;
}
return key;
}
constexpr inline uint16_t alphaNum3CodeToKey(QStringView code)
{
return alphaNum3CodeToKey(code, code.size());
}
template<typename T>
constexpr inline uint16_t alpha3CodeToKey(T code, std::size_t size)
{
return (size == 3 && isAlpha(code[0]) && isAlpha(code[1]) && isAlpha(code[2])) ? alphaNum3CodeToKey(code, 3) : 0;
}
template<std::size_t N>
constexpr inline uint16_t alpha3CodeToKey(const char (&code)[N])
{
return alpha3CodeToKey(code, N - 1);
}
constexpr inline uint16_t alpha3CodeToKey(QStringView code)
{
return alpha3CodeToKey(code, code.size());
}
constexpr inline uint32_t subdivisionCodeToKey(const char *code, std::size_t size)
{
if (size < 4 || code[2] != '-') {
return 0;
}
const auto countryKey = alpha2CodeToKey(code, 2);
const auto subdivKey = alphaNum3CodeToKey(code + 3, size - 3);
return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0;
}
constexpr inline uint32_t subdivisionCodeToKey(QStringView code)
{
if (code.size() < 4 || code[2] != QLatin1Char('-')) {
return 0;
}
const auto countryKey = alpha2CodeToKey(code.left(2));
const auto subdivKey = alphaNum3CodeToKey(code.mid(3), code.size() - 3);
return countryKey > 0 && subdivKey > 0 ? ((uint32_t)(countryKey) << 16 | subdivKey) : 0;
}
template<std::size_t N>
constexpr inline uint32_t subdivisionCodeToKey(const char (&code)[N])
{
return subdivisionCodeToKey(code, N - 1);
}
/// Before v4.16 iso-codes used for parent code just the subdivision code, without the country
/// (only by error sometimes). Since that version the full code now is always used.
/// Handle both cases gracefully.
/// Does not check the country part for sanity, but just discards the info.
constexpr inline uint16_t parentCodeToKey(QStringView code)
{
if (code.size() < 4) {
return alphaNum3CodeToKey(code);
}
if (code[2] != QLatin1Char('-')) {
return 0;
}
const auto countryKey = alpha2CodeToKey(code.left(2));
const auto subdivKey = alphaNum3CodeToKey(code.mid(3), code.size() - 3);
return countryKey > 0 ? subdivKey : 0;
}
}
#endif // ISOCODES_P_H
@@ -0,0 +1,334 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "config-localedata.h"
#include "isocodes_p.h"
#include "isocodescache_p.h"
#include "logging.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QStandardPaths>
// increment those when changing the format
enum : uint32_t {
Iso3166_1CacheHeader = 0x4B493101,
Iso3166_2CacheHeader = 0x4B493201,
};
static QString isoCodesPath(QStringView file)
{
#ifndef Q_OS_ANDROID
auto path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("iso-codes/json/") + file, QStandardPaths::LocateFile);
if (!path.isEmpty()) {
return path;
}
// search manually in the compile-time determined prefix
// needed for example for non-installed Windows binaries to work, such as unit tests
for (const char *installLocation : {"/share", "/bin/data"}) {
path = QLatin1String(ISO_CODES_PREFIX) + QLatin1String(installLocation) + QLatin1String("/iso-codes/json/") + file;
if (QFileInfo::exists(path)) {
return path;
}
}
return {};
#else
return QLatin1String("assets:/share/iso-codes/json/") + file;
#endif
}
static QString cachePath()
{
return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.ki18n/iso-codes/");
}
static QString cacheFilePath(QStringView file)
{
return cachePath() + file;
}
IsoCodesCache::~IsoCodesCache() = default;
IsoCodesCache *IsoCodesCache::instance()
{
static IsoCodesCache s_cache;
return &s_cache;
}
void IsoCodesCache::loadIso3166_1()
{
if (!m_iso3166_1CacheData && !loadIso3166_1Cache()) {
QDir().mkpath(cachePath());
createIso3166_1Cache(isoCodesPath(u"iso_3166-1.json"), cacheFilePath(u"iso_3166-1"));
loadIso3166_1Cache();
}
}
static std::unique_ptr<QFile> openCacheFile(QStringView cacheFileName, QStringView isoCodesFileName)
{
QFileInfo jsonFi(isoCodesPath(isoCodesFileName));
if (!jsonFi.exists()) { // no source file means we can only use an embedded cache
auto f = std::make_unique<QFile>(QLatin1String(":/org.kde.ki18n/iso-codes/cache/") + cacheFileName);
if (!f->open(QFile::ReadOnly) || f->size() < 8) {
return {};
}
return f;
}
auto f = std::make_unique<QFile>(cacheFilePath(cacheFileName));
if (!f->open(QFile::ReadOnly) || f->fileTime(QFile::FileModificationTime) < jsonFi.lastModified() || f->size() < 8) {
return {};
}
return f;
}
bool IsoCodesCache::loadIso3166_1Cache()
{
auto f = openCacheFile(u"iso_3166-1", u"iso_3166-1.json");
if (!f) {
return false;
}
m_iso3166_1CacheSize = f->size();
// validate cache file is usable
// header matches
const auto data = f->map(0, m_iso3166_1CacheSize);
if (*reinterpret_cast<const uint32_t *>(data) != Iso3166_1CacheHeader) {
return false;
}
// lookup tables fit into the available size
const auto size = *(reinterpret_cast<const uint32_t *>(data) + 1);
if (sizeof(Iso3166_1CacheHeader) + sizeof(size) + size * sizeof(MapEntry<uint16_t>) * 2 >= m_iso3166_1CacheSize) {
return false;
}
// string table is 0 terminated
if (data[m_iso3166_1CacheSize - 1] != '\0') {
return false;
}
m_iso3166_1CacheFile = std::move(f);
m_iso3166_1CacheData = data;
return true;
}
uint32_t IsoCodesCache::countryCount() const
{
return m_iso3166_1CacheData ? *(reinterpret_cast<const uint32_t *>(m_iso3166_1CacheData) + 1) : 0;
}
const MapEntry<uint16_t> *IsoCodesCache::countryNameMapBegin() const
{
return m_iso3166_1CacheData ? reinterpret_cast<const MapEntry<uint16_t> *>(m_iso3166_1CacheData + sizeof(uint32_t) * 2) : nullptr;
}
const MapEntry<uint16_t> *IsoCodesCache::countryAlpha3MapBegin() const
{
return m_iso3166_1CacheData ? countryNameMapBegin() + countryCount() : nullptr;
}
const char *IsoCodesCache::countryStringTableLookup(uint16_t offset) const
{
if (m_iso3166_1CacheData) {
const auto pos = offset + 2 * sizeof(uint32_t) + 2 * countryCount() * sizeof(MapEntry<uint16_t>);
return m_iso3166_1CacheSize > pos ? reinterpret_cast<const char *>(m_iso3166_1CacheData + pos) : nullptr;
}
return nullptr;
}
void IsoCodesCache::createIso3166_1Cache(const QString &isoCodesPath, const QString &cacheFilePath)
{
qCDebug(KI18NLD) << "Rebuilding ISO 3166-1 cache";
QFile file(isoCodesPath);
if (!file.open(QFile::ReadOnly)) {
qCWarning(KI18NLD) << "Unable to open iso_3166-1.json" << isoCodesPath << file.errorString();
return;
}
std::vector<MapEntry<uint16_t>> alpha2NameMap;
std::vector<MapEntry<uint16_t>> alpha3alpha2Map;
QByteArray iso3166_1stringTable;
const auto doc = QJsonDocument::fromJson(file.readAll());
const auto array = doc.object().value(QLatin1String("3166-1")).toArray();
for (const auto &entryVal : array) {
const auto entry = entryVal.toObject();
const auto alpha2 = entry.value(QLatin1String("alpha_2")).toString();
if (alpha2.size() != 2) {
continue;
}
const auto alpha2Key = IsoCodes::alpha2CodeToKey(alpha2);
assert(std::numeric_limits<uint16_t>::max() > iso3166_1stringTable.size());
alpha2NameMap.push_back({alpha2Key, (uint16_t)iso3166_1stringTable.size()});
iso3166_1stringTable.append(entry.value(QLatin1String("name")).toString().toUtf8());
iso3166_1stringTable.append('\0');
const auto alpha3Key = IsoCodes::alpha3CodeToKey(entry.value(QLatin1String("alpha_3")).toString());
alpha3alpha2Map.push_back({alpha3Key, alpha2Key});
}
std::sort(alpha2NameMap.begin(), alpha2NameMap.end());
std::sort(alpha3alpha2Map.begin(), alpha3alpha2Map.end());
// write out binary cache file
QFile cache(cacheFilePath);
if (!cache.open(QFile::WriteOnly)) {
qCWarning(KI18NLD) << "Failed to write ISO 3166-1 cache:" << cache.errorString() << cache.fileName();
return;
}
uint32_t n = Iso3166_1CacheHeader;
cache.write(reinterpret_cast<const char *>(&n), 4); // header
n = alpha2NameMap.size();
cache.write(reinterpret_cast<const char *>(&n), 4); // size
for (auto entry : alpha2NameMap) {
cache.write(reinterpret_cast<const char *>(&entry), sizeof(entry));
}
for (auto entry : alpha3alpha2Map) {
cache.write(reinterpret_cast<const char *>(&entry), sizeof(entry));
}
cache.write(iso3166_1stringTable);
}
void IsoCodesCache::loadIso3166_2()
{
if (!m_iso3166_2CacheData && !loadIso3166_2Cache()) {
QDir().mkpath(cachePath());
createIso3166_2Cache(isoCodesPath(u"iso_3166-2.json"), cacheFilePath(u"iso_3166-2"));
loadIso3166_2Cache();
}
}
bool IsoCodesCache::loadIso3166_2Cache()
{
auto f = openCacheFile(u"iso_3166-2", u"iso_3166-2.json");
if (!f) {
return false;
}
m_iso3166_2CacheSize = f->size();
// validate cache file is usable
// header matches
const auto data = f->map(0, m_iso3166_2CacheSize);
if (*reinterpret_cast<const uint32_t *>(data) != Iso3166_2CacheHeader) {
return false;
}
// name lookup table fits into the available size
auto size = *(reinterpret_cast<const uint32_t *>(data) + 1);
auto offset = 3 * sizeof(uint32_t) + size * sizeof(MapEntry<uint32_t>);
if (offset >= m_iso3166_2CacheSize) {
return false;
}
// hierarchy map boundary check
size = *(reinterpret_cast<const uint32_t *>(data + offset) - 1);
offset += size * sizeof(MapEntry<uint32_t>);
if (offset >= m_iso3166_2CacheSize) {
return false;
}
// string table is 0 terminated
if (data[m_iso3166_2CacheSize - 1] != '\0') {
return false;
}
m_iso3166_2CacheFile = std::move(f);
m_iso3166_2CacheData = data;
return true;
}
uint32_t IsoCodesCache::subdivisionCount() const
{
return m_iso3166_2CacheData ? *(reinterpret_cast<const uint32_t *>(m_iso3166_2CacheData) + 1) : 0;
}
const MapEntry<uint32_t> *IsoCodesCache::subdivisionNameMapBegin() const
{
return m_iso3166_2CacheData ? reinterpret_cast<const MapEntry<uint32_t> *>(m_iso3166_2CacheData + 2 * sizeof(uint32_t)) : nullptr;
}
uint32_t IsoCodesCache::subdivisionHierachyMapSize() const
{
return m_iso3166_2CacheData
? *(reinterpret_cast<const uint32_t *>(m_iso3166_2CacheData + 2 * sizeof(uint32_t) + subdivisionCount() * sizeof(MapEntry<uint32_t>)))
: 0;
}
const MapEntry<uint32_t> *IsoCodesCache::subdivisionParentMapBegin() const
{
return m_iso3166_2CacheData
? reinterpret_cast<const MapEntry<uint32_t> *>(m_iso3166_2CacheData + 3 * sizeof(uint32_t) + subdivisionCount() * sizeof(MapEntry<uint32_t>))
: nullptr;
}
const char *IsoCodesCache::subdivisionStringTableLookup(uint16_t offset) const
{
if (m_iso3166_2CacheData) {
const auto pos = offset + 3 * sizeof(uint32_t) + (subdivisionCount() + subdivisionHierachyMapSize()) * sizeof(MapEntry<uint32_t>);
return m_iso3166_2CacheSize > pos ? reinterpret_cast<const char *>(m_iso3166_2CacheData + pos) : nullptr;
}
return nullptr;
}
void IsoCodesCache::createIso3166_2Cache(const QString &isoCodesPath, const QString &cacheFilePath)
{
qCDebug(KI18NLD) << "Rebuilding ISO 3166-2 cache";
QFile file(isoCodesPath);
if (!file.open(QFile::ReadOnly)) {
qCWarning(KI18NLD) << "Unable to open iso_3166-2.json" << isoCodesPath << file.errorString();
return;
}
std::vector<MapEntry<uint32_t>> subdivNameMap;
std::vector<MapEntry<uint32_t>> subdivParentMap;
QByteArray iso3166_2stringTable;
const auto doc = QJsonDocument::fromJson(file.readAll());
const auto array = doc.object().value(QLatin1String("3166-2")).toArray();
for (const auto &entryVal : array) {
const auto entry = entryVal.toObject();
const auto key = IsoCodes::subdivisionCodeToKey(entry.value(QLatin1String("code")).toString());
assert(std::numeric_limits<uint16_t>::max() > iso3166_2stringTable.size());
subdivNameMap.push_back({key, (uint16_t)iso3166_2stringTable.size()});
iso3166_2stringTable.append(entry.value(QLatin1String("name")).toString().toUtf8());
iso3166_2stringTable.append('\0');
const auto parentKey = IsoCodes::parentCodeToKey(entry.value(QLatin1String("parent")).toString());
if (parentKey) {
subdivParentMap.push_back({key, parentKey});
}
}
std::sort(subdivNameMap.begin(), subdivNameMap.end());
std::sort(subdivParentMap.begin(), subdivParentMap.end());
// write out binary cache file
QFile cache(cacheFilePath);
if (!cache.open(QFile::WriteOnly)) {
qCWarning(KI18NLD) << "Failed to write ISO 3166-2 cache:" << cache.errorString() << cache.fileName();
return;
}
uint32_t n = Iso3166_2CacheHeader;
cache.write(reinterpret_cast<const char *>(&n), 4); // header
n = subdivNameMap.size();
cache.write(reinterpret_cast<const char *>(&n), 4); // size of the name map
for (auto entry : subdivNameMap) {
cache.write(reinterpret_cast<const char *>(&entry), sizeof(entry));
}
n = subdivParentMap.size();
cache.write(reinterpret_cast<const char *>(&n), 4); // size of the hierarchy map
for (auto entry : subdivParentMap) {
cache.write(reinterpret_cast<const char *>(&entry), sizeof(entry));
}
cache.write(iso3166_2stringTable);
}
@@ -0,0 +1,10 @@
<!--
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: CC0-1.0
-->
<RCC>
<qresource prefix="/org.kde.ki18n/iso-codes/cache/">
<file>iso_3166-1</file>
<file>iso_3166-2</file>
</qresource>
</RCC>
@@ -0,0 +1,74 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef ISOCODESCACHE_P_H
#define ISOCODESCACHE_P_H
#include "mapentry_p.h"
#include <QByteArray>
#include <QStringView>
#include <cstdint>
#include <memory>
#include <vector>
class QFile;
/** Cache for iso-codes JSON data. */
class IsoCodesCache
{
public:
~IsoCodesCache();
static IsoCodesCache *instance();
void loadIso3166_1();
void loadIso3166_2();
uint32_t countryCount() const;
const MapEntry<uint16_t> *countryNameMapBegin() const;
inline const MapEntry<uint16_t> *countryNameMapEnd() const
{
return countryNameMapBegin() + countryCount();
}
const MapEntry<uint16_t> *countryAlpha3MapBegin() const;
inline const MapEntry<uint16_t> *countryAlpha3MapEnd() const
{
return countryAlpha3MapBegin() + countryCount();
}
const char *countryStringTableLookup(uint16_t offset) const;
uint32_t subdivisionCount() const;
const MapEntry<uint32_t> *subdivisionNameMapBegin() const;
inline const MapEntry<uint32_t> *subdivisionNameMapEnd() const
{
return subdivisionNameMapBegin() + subdivisionCount();
}
uint32_t subdivisionHierachyMapSize() const;
const MapEntry<uint32_t> *subdivisionParentMapBegin() const;
inline const MapEntry<uint32_t> *subdivisionParentMapEnd() const
{
return subdivisionParentMapBegin() + subdivisionHierachyMapSize();
}
const char *subdivisionStringTableLookup(uint16_t offset) const;
static void createIso3166_1Cache(const QString &isoCodesPath, const QString &cacheFilePath);
static void createIso3166_2Cache(const QString &isoCodesPath, const QString &cacheFilePath);
private:
bool loadIso3166_1Cache();
bool loadIso3166_2Cache();
std::unique_ptr<QFile> m_iso3166_1CacheFile;
const uint8_t *m_iso3166_1CacheData = nullptr;
std::size_t m_iso3166_1CacheSize = 0;
std::unique_ptr<QFile> m_iso3166_2CacheFile;
const uint8_t *m_iso3166_2CacheData = nullptr;
std::size_t m_iso3166_2CacheSize = 0;
};
#endif // ISOCODESCACHE_H
@@ -0,0 +1,428 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcountry.h"
#include "isocodes_p.h"
#include "isocodescache_p.h"
#include "kcatalog_p.h"
#include "klocalizedstring.h"
#include "logging.h"
#include "spatial_index_p.h"
#include "timezonedata_p.h"
#include <cstring>
using namespace Qt::Literals;
static_assert(sizeof(KCountry) == 2);
KCountry::KCountry()
: d(0)
{
}
KCountry::KCountry(const KCountry &) = default;
KCountry::~KCountry() = default;
KCountry &KCountry::operator=(const KCountry &) = default;
bool KCountry::operator==(const KCountry &other) const
{
return d == other.d;
}
bool KCountry::operator!=(const KCountry &other) const
{
return d != other.d;
}
bool KCountry::isValid() const
{
return d != 0;
}
QString KCountry::alpha2() const
{
if (d == 0) {
return {};
}
QString code(2, QLatin1Char('\0'));
code[0] = QLatin1Char(d >> 8);
code[1] = QLatin1Char(d & 0xff);
return code;
}
QString KCountry::alpha3() const
{
const auto cache = IsoCodesCache::instance();
const auto it = std::find_if(cache->countryAlpha3MapBegin(), cache->countryAlpha3MapEnd(), [this](auto entry) {
return entry.value == d;
});
if (it != cache->countryAlpha3MapEnd()) {
uint16_t alpha3Key = (*it).key;
QString code(3, QLatin1Char('\0'));
code[2] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
alpha3Key /= IsoCodes::AlphaNumKeyFactor;
code[1] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
alpha3Key /= IsoCodes::AlphaNumKeyFactor;
code[0] = QLatin1Char(IsoCodes::mapFromAlphaNumKey(alpha3Key));
return code;
}
return {};
}
QString KCountry::name() const
{
if (d == 0) {
return {};
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_1();
const auto it = std::lower_bound(cache->countryNameMapBegin(), cache->countryNameMapEnd(), d);
if (it != cache->countryNameMapEnd() && (*it).key == d) {
return i18nd("iso_3166-1", cache->countryStringTableLookup((*it).value));
}
return {};
}
QString KCountry::emojiFlag() const
{
if (d == 0) {
return {};
}
QString flag;
char flagA[] = "\xF0\x9F\x87\xA6";
flagA[3] = 0xA6 + ((d >> 8) - 'A');
flag += QString::fromUtf8(flagA);
flagA[3] = 0xA6 + ((d & 0xff) - 'A');
flag += QString::fromUtf8(flagA);
return flag;
}
QLocale::Country KCountry::country() const
{
if (d == 0) {
return QLocale::AnyCountry;
}
return QLocale::codeToTerritory(alpha2());
}
QList<const char *> KCountry::timeZoneIds() const
{
QList<const char *> tzs;
if (d == 0) {
return tzs;
}
const auto countryIt = std::lower_bound(TimezoneData::countryTimezoneMapBegin(), TimezoneData::countryTimezoneMapEnd(), d);
if (countryIt != TimezoneData::countryTimezoneMapEnd() && (*countryIt).key == d) {
tzs.push_back(TimezoneData::ianaIdLookup((*countryIt).value));
return tzs;
}
const auto [subdivBegin, subdivEnd] =
std::equal_range(TimezoneData::subdivisionTimezoneMapBegin(), TimezoneData::subdivisionTimezoneMapEnd(), d, [](auto lhs, auto rhs) {
if constexpr (std::is_same_v<decltype(lhs), uint16_t>)
return lhs < (rhs.key >> 16);
else
return (lhs.key >> 16) < rhs;
});
for (auto it = subdivBegin; it != subdivEnd; ++it) {
const auto tzId = TimezoneData::ianaIdLookup((*it).value);
if (!tzs.contains(tzId)) {
tzs.push_back(tzId);
}
}
return tzs;
}
QString KCountry::currencyCode() const
{
if (d == 0) {
return {};
}
QString currency;
const auto ls = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, country());
for (const auto &l : ls) {
if (currency.isEmpty()) {
currency = l.currencySymbol(QLocale::CurrencyIsoCode);
} else if (currency != l.currencySymbol(QLocale::CurrencyIsoCode)) {
qCDebug(KI18NLD) << "conflicting currency information in QLocale for" << alpha2();
return {};
}
}
return currency;
}
QList<KCountrySubdivision> KCountry::subdivisions() const
{
if (d == 0) {
return {};
}
QList<KCountrySubdivision> l;
auto cache = IsoCodesCache::instance();
cache->loadIso3166_2();
// we don't have a country->subdivisions map, instead we use the full list of subdivisions
// (which is sorted by country due to the country being in the two most significant bytes of its key),
// and check the child->parent subdivision map for root elements
auto it = std::lower_bound(cache->subdivisionNameMapBegin(), cache->subdivisionNameMapEnd(), d, [](auto lhs, auto rhs) {
return (lhs.key >> 16) < rhs;
});
auto [parentBegin, parentEnd] = std::equal_range(cache->subdivisionParentMapBegin(), cache->subdivisionParentMapEnd(), d, [](auto lhs, auto rhs) {
if constexpr (std::is_same_v<decltype(lhs), uint16_t>)
return lhs < (rhs.key >> 16);
else
return (lhs.key >> 16) < rhs;
});
for (; it != cache->subdivisionNameMapEnd() && ((*it).key >> 16) == d; ++it) {
if (!std::binary_search(parentBegin, parentEnd, (*it).key)) {
KCountrySubdivision s;
s.d = (*it).key;
l.push_back(s);
}
}
return l;
}
static uint16_t validatedAlpha2Key(uint16_t alpha2Key)
{
if (!alpha2Key) {
return 0;
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_1();
const auto it = std::lower_bound(cache->countryNameMapBegin(), cache->countryNameMapEnd(), alpha2Key);
if (it != cache->countryNameMapEnd() && (*it).key == alpha2Key) {
return alpha2Key;
}
return 0;
}
KCountry KCountry::fromAlpha2(QStringView alpha2Code)
{
KCountry c;
c.d = validatedAlpha2Key(IsoCodes::alpha2CodeToKey(alpha2Code));
return c;
}
KCountry KCountry::fromAlpha2(const char *alpha2Code)
{
KCountry c;
if (!alpha2Code) {
return c;
}
c.d = validatedAlpha2Key(IsoCodes::alpha2CodeToKey(alpha2Code, std::strlen(alpha2Code)));
return c;
}
static uint16_t alpha3Lookup(uint16_t alpha3Key)
{
if (!alpha3Key) {
return 0;
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_1();
const auto it = std::lower_bound(cache->countryAlpha3MapBegin(), cache->countryAlpha3MapEnd(), alpha3Key);
if (it != cache->countryAlpha3MapEnd() && (*it).key == alpha3Key) {
return (*it).value;
}
return 0;
}
KCountry KCountry::fromAlpha3(QStringView alpha3Code)
{
KCountry c;
c.d = alpha3Lookup(IsoCodes::alpha3CodeToKey(alpha3Code));
return c;
}
KCountry KCountry::fromAlpha3(const char *alpha3Code)
{
KCountry c;
if (!alpha3Code) {
return c;
}
c.d = alpha3Lookup(IsoCodes::alpha3CodeToKey(alpha3Code, std::strlen(alpha3Code)));
return c;
}
KCountry KCountry::fromLocation(float latitude, float longitude)
{
const auto entry = SpatialIndex::lookup(latitude, longitude);
KCountry c;
c.d = entry.m_subdiv >> 16;
return c;
}
KCountry KCountry::fromQLocale(QLocale::Country country)
{
return fromAlpha2(QLocale::territoryToCode(country).data());
}
static QString normalizeCountryName(QStringView name)
{
QString res;
res.reserve(name.size());
for (const auto c : name) {
// the following needs to be done fairly fine-grained, as this can easily mess up scripts
// that rely on some non-letter characters to work
// all values used below were obtained by similar code in KContacts, which used to do
// a full offline pre-computation of this and checked for ambiguities introduced by too
// aggressive normalization
switch (c.category()) {
// strip decorative elements that don't contribute to identification (parenthesis, dashes, quotes, etc)
case QChar::Punctuation_Connector:
case QChar::Punctuation_Dash:
case QChar::Punctuation_Open:
case QChar::Punctuation_Close:
case QChar::Punctuation_InitialQuote:
case QChar::Punctuation_FinalQuote:
case QChar::Punctuation_Other:
continue;
default:
break;
}
if (c.isSpace()) {
if (!res.isEmpty() && !res.back().isSpace()) {
res.push_back(' '_L1);
}
continue;
}
// if the character has a canonical decomposition skip the combining diacritic markers following it
// this works particularly well for Latin, but messes up Hangul
if (c.script() != QChar::Script_Hangul && c.decompositionTag() == QChar::Canonical) {
res.push_back(c.decomposition().at(0).toCaseFolded());
} else {
res.push_back(c.toCaseFolded());
}
}
return res.trimmed();
}
// check is @p needle is a space-separated substring of haystack
static bool isSeparatedSubstring(QStringView haystack, QStringView needle)
{
auto idx = haystack.indexOf(needle);
if (idx < 0) {
return false;
}
if (idx > 0 && !haystack[idx - 1].isSpace()) {
return false;
}
idx += needle.size();
return idx >= haystack.size() || haystack[idx].isSpace();
}
static void checkSubstringMatch(QStringView lhs, QStringView rhs, uint16_t code, uint16_t &result)
{
if (result == std::numeric_limits<uint16_t>::max() || result == code || rhs.isEmpty()) {
return;
}
const auto matches = isSeparatedSubstring(lhs, rhs) || isSeparatedSubstring(rhs, lhs);
if (!matches) {
return;
}
result = result == 0 ? code : std::numeric_limits<uint16_t>::max();
}
KCountry KCountry::fromName(QStringView name)
{
if (name.isEmpty()) {
return {};
}
const auto normalizedName = normalizeCountryName(name);
auto cache = IsoCodesCache::instance();
cache->loadIso3166_1();
uint16_t substrMatch = 0;
// check untranslated names
for (auto it = cache->countryNameMapBegin(); it != cache->countryNameMapEnd(); ++it) {
const auto normalizedCountry = normalizeCountryName(QString::fromUtf8(cache->countryStringTableLookup((*it).value)));
if (normalizedName == normalizedCountry) {
KCountry c;
c.d = (*it).key;
return c;
}
checkSubstringMatch(normalizedName, normalizedCountry, (*it).key, substrMatch);
}
// check translated names
const auto langs = KCatalog::availableCatalogLanguages("iso_3166-1");
for (const auto &lang : langs) {
const auto catalog = KCatalog("iso_3166-1", lang);
for (auto it = cache->countryNameMapBegin(); it != cache->countryNameMapEnd(); ++it) {
const auto normalizedCountry = normalizeCountryName(catalog.translate(cache->countryStringTableLookup((*it).value)));
if (normalizedName == normalizedCountry) {
KCountry c;
c.d = (*it).key;
return c;
}
checkSubstringMatch(normalizedName, normalizedCountry, (*it).key, substrMatch);
}
}
// unique prefix/suffix match
if (substrMatch != std::numeric_limits<uint16_t>::max() && substrMatch != 0) {
KCountry c;
c.d = substrMatch;
return c;
}
// fallback to code lookups
if (normalizedName.size() == 3) {
return fromAlpha3(normalizedName);
}
if (normalizedName.size() == 2) {
return fromAlpha2(normalizedName);
}
return {};
}
QList<KCountry> KCountry::allCountries()
{
QList<KCountry> l;
auto cache = IsoCodesCache::instance();
cache->loadIso3166_1();
l.reserve(cache->countryCount());
std::transform(cache->countryNameMapBegin(), cache->countryNameMapEnd(), std::back_inserter(l), [](auto entry) {
KCountry c;
c.d = entry.key;
return c;
});
return l;
}
QStringList KCountry::timeZoneIdsStringList() const
{
const auto tzIds = timeZoneIds();
QStringList l;
l.reserve(tzIds.size());
std::transform(tzIds.begin(), tzIds.end(), std::back_inserter(l), [](const char *tzId) {
return QString::fromUtf8(tzId);
});
return l;
}
#include "moc_kcountry.cpp"
@@ -0,0 +1,124 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOUNTRY_H
#define KCOUNTRY_H
#include "ki18nlocaledata_export.h"
#include <QLocale>
#include <QMetaType>
#include "kcountrysubdivision.h"
class KCountry;
namespace KTimeZone
{
KI18NLOCALEDATA_EXPORT KCountry country(const char *);
}
/**
* @class KCountry kcountry.h <KCountry>
*
* Information about an ISO 3166-1 country.
*
* The information provided here are aggregated from the following sources:
* - [iso-codes](https://salsa.debian.org/iso-codes-team/iso-codes/)
* - [timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder/)
* - [OSM](https://openstreetmap.org)
* - [CLDR](http://cldr.unicode.org/)
*
* @note This requires the iso-codes data files and translation catalogs to be available at runtime.
*
* @since 5.88
*/
class KI18NLOCALEDATA_EXPORT KCountry
{
Q_GADGET
Q_PROPERTY(QString alpha2 READ alpha2)
Q_PROPERTY(QString alpha3 READ alpha3)
Q_PROPERTY(QString name READ name)
Q_PROPERTY(QString emojiFlag READ emojiFlag)
Q_PROPERTY(QString currencyCode READ currencyCode)
Q_PROPERTY(QList<KCountrySubdivision> subdivisions READ subdivisions)
Q_PROPERTY(QStringList timeZoneIds READ timeZoneIdsStringList)
public:
/** Creates an invalid/empty KCountry instance.
* See the fromX() methods for creating a valid instance.
*/
KCountry();
KCountry(const KCountry &);
~KCountry();
KCountry &operator=(const KCountry &);
bool operator==(const KCountry &other) const;
bool operator!=(const KCountry &other) const;
/** Returns @c false if this is an empty/invalid/default constructed instance, @c true otherwise. */
bool isValid() const;
/** ISO 3166-1 alpha 2 country code. */
QString alpha2() const;
/** ISO 3166-1 alpha 3 country code. */
QString alpha3() const;
/** Translated country name. */
QString name() const;
/** Returns the Unicode flag emoji for this country. */
QString emojiFlag() const;
/** Returns the QLocale::Country value matching this country, or QLocale::AnyCountry if there is none. */
QLocale::Country country() const; // TODO better name?
/** Timezones in use in this country. */
QList<const char *> timeZoneIds() const;
/** Currency used in this country as ISO 4217 code. */
QString currencyCode() const;
/** Highest level of ISO 3166-2 country subdivisions.
* If there is only one level of subdivisions this lists all of them,
* for countries with multiple levels, this only includes the top-level
* subdivisions (ie. those having no parent subdivision).
* @note: This can be empty.
*/
QList<KCountrySubdivision> subdivisions() const;
/** Create a KCountry instance from an ISO 3166-1 alpha 2 code. */
static KCountry fromAlpha2(QStringView alpha2Code);
/** Create a KCountry instance from an ISO 3166-1 alpha 2 code. */
static KCountry fromAlpha2(const char *alpha2Code);
/** Create a KCountry instance from an ISO 3166-1 alpha 3 code. */
static KCountry fromAlpha3(QStringView alpha3Code);
/** Create a KCountry instance from an ISO 3166-1 alpha 3 code. */
static KCountry fromAlpha3(const char *alpha3Code);
/** Looks up the country at the given geographic coordinate.
* This can return an invalid object if the country could not be determined. This can happen in a number of cases:
* - on oceans
* - in polar regions
* - close to a land border
* - in disputed territories
*/
static KCountry fromLocation(float latitude, float longitude);
/** Returns a KCountry instance matching the given QLocale::Country code. */
static KCountry fromQLocale(QLocale::Country country);
/** Attempts to identify the country from the given name.
* The name can be in any language.
*/
static KCountry fromName(QStringView name);
/** List all countries. */
static QList<KCountry> allCountries();
private:
KI18NLOCALEDATA_NO_EXPORT QStringList timeZoneIdsStringList() const;
friend class KCountrySubdivision;
friend KI18NLOCALEDATA_EXPORT KCountry KTimeZone::country(const char *);
uint16_t d;
};
Q_DECLARE_TYPEINFO(KCountry, Q_RELOCATABLE_TYPE);
#endif // KCOUNTRY_H
@@ -0,0 +1,210 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcountrysubdivision.h"
#include "isocodes_p.h"
#include "isocodescache_p.h"
#include "kcountry.h"
#include "ki18n_logging.h"
#include "klocalizedstring.h"
#include "spatial_index_p.h"
#include "timezonedata_p.h"
#include <cstring>
static_assert(sizeof(KCountrySubdivision) == 4);
KCountrySubdivision::KCountrySubdivision()
: d(0)
{
}
KCountrySubdivision::KCountrySubdivision(const KCountrySubdivision &) = default;
KCountrySubdivision::~KCountrySubdivision() = default;
KCountrySubdivision &KCountrySubdivision::operator=(const KCountrySubdivision &) = default;
bool KCountrySubdivision::operator==(const KCountrySubdivision &other) const
{
return d == other.d;
}
bool KCountrySubdivision::operator!=(const KCountrySubdivision &other) const
{
return d != other.d;
}
bool KCountrySubdivision::isValid() const
{
return d != 0;
}
QString KCountrySubdivision::code() const
{
if (d == 0) {
return {};
}
QString code;
code.reserve(6);
code += country().alpha2();
code += QLatin1Char('-');
auto key = d & 0xffff;
while (key) {
const auto c = IsoCodes::mapFromAlphaNumKey(key);
if (c) {
code.insert(3, QLatin1Char(c));
}
key /= IsoCodes::AlphaNumKeyFactor;
}
return code;
}
QString KCountrySubdivision::name() const
{
if (d == 0) {
return {};
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_2();
const auto it = std::lower_bound(cache->subdivisionNameMapBegin(), cache->subdivisionNameMapEnd(), d);
if (it != cache->subdivisionNameMapEnd() && (*it).key == d) {
return i18nd("iso_3166-2", cache->subdivisionStringTableLookup((*it).value));
}
return {};
}
KCountry KCountrySubdivision::country() const
{
KCountry c;
c.d = d >> 16;
return c;
}
KCountrySubdivision KCountrySubdivision::parent() const
{
KCountrySubdivision s;
if (d == 0) {
return s;
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_2();
const auto it = std::lower_bound(cache->subdivisionParentMapBegin(), cache->subdivisionParentMapEnd(), d);
if (it != cache->subdivisionParentMapEnd() && (*it).key == d) {
s.d = (d & 0xffff0000) | (uint32_t)(*it).value;
}
return s;
}
QList<const char *> KCountrySubdivision::timeZoneIds() const
{
QList<const char *> tzs;
if (d == 0) {
return tzs;
}
const auto [subdivBegin, subdivEnd] = std::equal_range(TimezoneData::subdivisionTimezoneMapBegin(), TimezoneData::subdivisionTimezoneMapEnd(), d);
if (subdivBegin != subdivEnd) {
tzs.reserve(std::distance(subdivBegin, subdivEnd));
for (auto it = subdivBegin; it != subdivEnd; ++it) {
tzs.push_back(TimezoneData::ianaIdLookup((*it).value));
}
return tzs;
}
const auto countryIt = std::lower_bound(TimezoneData::countryTimezoneMapBegin(), TimezoneData::countryTimezoneMapEnd(), uint16_t(d >> 16));
if (countryIt != TimezoneData::countryTimezoneMapEnd() && (*countryIt).key == (d >> 16)) {
tzs.push_back(TimezoneData::ianaIdLookup((*countryIt).value));
}
return tzs;
}
QList<KCountrySubdivision> KCountrySubdivision::subdivisions() const
{
if (d == 0) {
return {};
}
QList<KCountrySubdivision> l;
auto cache = IsoCodesCache::instance();
cache->loadIso3166_2();
// we don't have a parent->child map, instead we use the child->parent map
// that is sorted by country (due to the country being in the two most significant bytes of its key),
// so we don't need to do a full sequential search here
auto it = std::lower_bound(cache->subdivisionParentMapBegin(), cache->subdivisionParentMapEnd(), d >> 16, [](auto lhs, auto rhs) {
return (lhs.key >> 16) < rhs;
});
for (; it != cache->subdivisionParentMapEnd() && ((*it).key >> 16) == (d >> 16); ++it) {
if ((*it).value == (d & 0xffff)) {
KCountrySubdivision s;
s.d = (*it).key;
l.push_back(s);
}
}
return l;
}
static uint32_t validatedSubdivisionKey(uint32_t key)
{
if (!key) {
return 0;
}
auto cache = IsoCodesCache::instance();
cache->loadIso3166_2();
const auto it = std::lower_bound(cache->subdivisionNameMapBegin(), cache->subdivisionNameMapEnd(), key);
if (it != cache->subdivisionNameMapEnd() && (*it).key == key) {
return key;
}
return 0;
}
KCountrySubdivision KCountrySubdivision::fromCode(QStringView code)
{
KCountrySubdivision s;
s.d = validatedSubdivisionKey(IsoCodes::subdivisionCodeToKey(code));
return s;
}
KCountrySubdivision KCountrySubdivision::fromCode(const char *code)
{
KCountrySubdivision s;
if (code) {
s.d = validatedSubdivisionKey(IsoCodes::subdivisionCodeToKey(code, std::strlen(code)));
}
return s;
}
KCountrySubdivision KCountrySubdivision::fromLocation(float latitude, float longitude)
{
const auto entry = SpatialIndex::lookup(latitude, longitude);
KCountrySubdivision s;
if (entry.m_subdiv & 0xffff) {
s.d = entry.m_subdiv;
}
return s;
}
QStringList KCountrySubdivision::timeZoneIdsStringList() const
{
const auto tzIds = timeZoneIds();
QStringList l;
l.reserve(tzIds.size());
std::transform(tzIds.begin(), tzIds.end(), std::back_inserter(l), [](const char *tzId) {
return QString::fromUtf8(tzId);
});
return l;
}
#include "moc_kcountrysubdivision.cpp"
@@ -0,0 +1,95 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOUNTRYSUBDIVISION_H
#define KCOUNTRYSUBDIVISION_H
#include "ki18nlocaledata_export.h"
#include <QMetaType>
class KCountry;
/**
* @class KCountrySubdivision kcountrysubdivision.h <KCountrySubdivision>
*
* Information about an ISO 3166-2 country subdivision.
*
* @note This requires the [iso-codes](https://salsa.debian.org/iso-codes-team/iso-codes/)
* data files and translation catalogs to be available at runtime.
* @see KCountry for the data sources.
*
* @since 5.88
*/
class KI18NLOCALEDATA_EXPORT KCountrySubdivision
{
Q_GADGET
Q_PROPERTY(QString code READ code)
Q_PROPERTY(QString name READ name)
Q_PROPERTY(KCountry country READ country)
Q_PROPERTY(KCountrySubdivision parent READ parent)
Q_PROPERTY(QList<KCountrySubdivision> subdivisions READ subdivisions)
Q_PROPERTY(QStringList timeZoneIds READ timeZoneIdsStringList)
public:
/** Creates an invalid/empty KCountrySubdivision instance.
* See the fromX() methods for creating a valid instance.
*/
KCountrySubdivision();
KCountrySubdivision(const KCountrySubdivision &);
~KCountrySubdivision();
KCountrySubdivision &operator=(const KCountrySubdivision &);
bool operator==(const KCountrySubdivision &other) const;
bool operator!=(const KCountrySubdivision &other) const;
/** Returns @c false if this is an empty/invalid/default constructed instance, @c true otherwise. */
bool isValid() const;
/** ISO 3166-2 country subdivision code. */
QString code() const;
/** Translated country subdivision name. */
QString name() const;
/** Country this subdivision belongs to. */
KCountry country() const;
/** Parent subdivision, if this is a subdivision of another subdivision.
* Returns an invalid element for top-level subdivisions.
*/
KCountrySubdivision parent() const;
/** Timezones in use in this country subdivision. */
// for subdivisions we have to generate that by polygon intersections in QGIS -> POC
QList<const char *> timeZoneIds() const;
/** Subdivisions of this subdivision, if any.
* This is only relevant for countries with multiple ISO 3166-2 subdivision levels.
*/
QList<KCountrySubdivision> subdivisions() const;
/** Create a KCountrySubdivision instance from an ISO 3166-2 code. */
static KCountrySubdivision fromCode(QStringView code);
/** Create a KCountrySubdivision instance from an ISO 3166-2 code. */
static KCountrySubdivision fromCode(const char *code);
/** Looks up the country subdivision at the given geographic coordinate.
* This can return an invalid object if the country subdivision could not be determined. This can happen in a number of cases:
* - on oceans
* - in polar regions
* - close to a land border
* - in disputed territories
* @note It is possible for KCountry::fromLocation() to return a valid result despite
* this method returning an invalid result.
*/
static KCountrySubdivision fromLocation(float latitude, float longitude);
private:
KI18NLOCALEDATA_NO_EXPORT QStringList timeZoneIdsStringList() const;
friend class KCountry;
uint32_t d;
};
Q_DECLARE_TYPEINFO(KCountrySubdivision, Q_RELOCATABLE_TYPE);
#endif // KCOUNTRYSUBDIVISION_H
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktimezone.h"
#include "data/timezone_name_table.cpp"
#include "kcountry.h"
#include "spatial_index_p.h"
#include <QTimeZone>
#include <cstring>
const char *KTimeZone::fromLocation(float latitude, float longitude)
{
const auto entry = SpatialIndex::lookup(latitude, longitude);
return timezone_name_table + entry.m_tz;
}
KCountry KTimeZone::country(const char *ianaId)
{
// Asia/Bangkok is special as it's the only "regular" IANA tz that covers more than one country
// (northern Vietnam and Thailand in this case), QTimeZone however reports it as Thailand.
if (!ianaId || std::strcmp(ianaId, "") == 0 || std::strcmp(ianaId, "Asia/Bangkok") == 0) {
return {};
}
return KCountry::fromQLocale(QTimeZone(ianaId).territory());
}
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTIMEZONE_H
#define KTIMEZONE_H
#include "ki18nlocaledata_export.h"
class KCountry;
/** Timezone localization methods.
* @since 5.88
*/
namespace KTimeZone // TODO name clash with kdelibs4support!?
{
/** Returns the timezone at the given geo coordinate. */
KI18NLOCALEDATA_EXPORT const char *fromLocation(float latitude, float longitude);
/** Returns the country a timezone is in.
* This only returns a country if the timezone covers exactly one country
* (but not necessarily the entire country).
* For obtaining any country covered by a timezone, see QTimeZone::territory.
*/
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId);
}
#endif // KTIMEZONE_H
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef MAPENTRY_P_H
#define MAPENTRY_P_H
#include <algorithm>
#include <cstdint>
#include <iterator>
#pragma pack(push)
#pragma pack(1)
template<typename KeyType>
struct MapEntry {
KeyType key;
uint16_t value;
};
template<typename KeyType>
constexpr inline bool operator<(MapEntry<KeyType> lhs, MapEntry<KeyType> rhs)
{
return lhs.key < rhs.key;
}
template<typename KeyType>
constexpr inline bool operator<(MapEntry<KeyType> lhs, KeyType rhs)
{
return lhs.key < rhs;
}
template<typename KeyType>
constexpr inline bool operator<(KeyType lhs, MapEntry<KeyType> rhs)
{
return lhs < rhs.key;
}
template<typename MapEntry, std::size_t N>
inline constexpr bool isSortedLookupTable(const MapEntry (&map)[N])
{
#if __cplusplus > 201703L && defined(__GNUC__) && __GNUC__ > 9 && !defined(__clang__)
return std::is_sorted(std::begin(map), std::end(map));
#else
(void)map;
return true;
#endif
}
#pragma pack(pop)
#endif
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
data
__pycache__
@@ -0,0 +1,108 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: LGPL-2.0-or-later
import os
import requests
from qgis import *
from qgis.core import *
import time
import zipfile
from config import *
# Download and unpack Shapefiles
class LayerDownloadTask(QgsTask):
def __init__(self, url, dest):
super().__init__('Download Shapefile', QgsTask.CanCancel)
self.url = url
self.dest = dest
def run(self):
try:
QgsMessageLog.logMessage(f"Downloading and unpacking {self.dest}...", LOG_CATEGORY, Qgis.Info)
if not os.path.exists(self.dest):
r = requests.get(self.url)
if r.status_code < 400:
with open(self.dest, 'wb') as f:
f.write(r.content)
with zipfile.ZipFile(self.dest, 'r') as z:
z.extractall('.')
QgsMessageLog.logMessage(f"Downloaded and unpacked {self.dest}.", LOG_CATEGORY, Qgis.Info)
except Exception as e:
QgsMessageLog.logMessage(f"Exception in task: {e}", LOG_CATEGORY, Qgis.Critical)
return True
# Load and simplify Shapefile layers
# Simplification is done to massively speed up geometry intersection computation
# (for reference: the original KItinerary tz spatial index took 8h to compute without simplification,
# and about 15 minutes with a Douglas Peucker simplification with a 0.001 threshold, with no practical
# loss of precision
class LoadLayerTask(QgsTask):
def __init__(self, url, fileName, context, layerName):
super().__init__(f"Loading layer {fileName}", QgsTask.CanCancel)
self.layer = None
self.url = url
self.fileName = fileName
self.context = context
self.layerName = layerName
self.downloadTask = LayerDownloadTask(url, fileName)
self.addSubTask(self.downloadTask, [], QgsTask.ParentDependsOnSubTask)
def run(self):
QgsMessageLog.logMessage(f"Simplifying layer {self.fileName}", LOG_CATEGORY, Qgis.Info)
fullLayer = QgsVectorLayer(self.fileName, f"{self.fileName}-full-resolution", 'ogr')
if not fullLayer.isValid():
QgsMessageLog.logMessage(f"Failed to load layer {self.fileName}!", LOG_CATEGORY, Qgis.Critical)
result = processing.run('qgis:simplifygeometries', {'INPUT': fullLayer, 'METHOD': 0, 'TOLERANCE': 0.001, 'OUTPUT': 'TEMPORARY_OUTPUT' })
self.layer = result['OUTPUT']
self.layer.setName(f"{self.fileName}-simplified")
self.context[self.layerName] = self.layer
QgsMessageLog.logMessage(f"Simplified layer {self.fileName}", LOG_CATEGORY, Qgis.Info)
return True
def finished(self, result):
QgsProject.instance().addMapLayer(self.layer)
# Filter out too small elements in the ISO 3166-2 layer
class Iso3166_2FilterTask(QgsTask):
def __init__(self, context):
super().__init__('Filtering ISO 3166-2 layer', QgsTask.CanCancel)
self.context = context
def run(self):
QgsMessageLog.logMessage('Filtering ISO 3166-2 layer', LOG_CATEGORY, Qgis.Info)
subdivLayer = self.context['subdivLayer']
toBeRemoved = []
for feature in subdivLayer.getFeatures():
# sic: the key is really "admin_leve" in the input file, due to length restrictions in the Shapefile...
level = feature['admin_leve']
country = feature['ISO3166-2'][:2]
if not isinstance(level, str) or not isinstance(country, str):
continue
for filter in ISO3166_2_FILTER:
if int(level) == filter['admin_level'] and country == filter['country']:
toBeRemoved.append(feature.id())
break
subdivLayer.dataProvider().deleteFeatures(toBeRemoved)
return True
# Setup all data layers we need
class LoadLayersTask(QgsTask):
def __init__(self, context):
super().__init__('Loading layers...', QgsTask.CanCancel)
self.context = context
self.tasks = [
LoadLayerTask(TZDATA_URL, f"timezones.shapefile-{TZDATA_VERSION}.zip", context, 'tzLayer'),
LoadLayerTask(ISO3166_1_URL, f"iso3166-1-boundaries.shp-{ISO3166_1_VERSION}.zip", context, 'countryLayer'),
LoadLayerTask(ISO3166_2_URL, f"iso3166-2-boundaries.shp-{ISO3166_2_VERSION}.zip", context, 'subdivLayer')
]
for task in self.tasks:
self.addSubTask(task, [], QgsTask.ParentDependsOnSubTask)
self.filterTask = Iso3166_2FilterTask(context)
self.addSubTask(self.filterTask, [self.tasks[2]], QgsTask.ParentDependsOnSubTask)
def run(self):
return True
@@ -0,0 +1,372 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: LGPL-2.0-or-later
from config import *
import datetime
import functools
import os
import pytz
import time
from qgis import *
from qgis.core import *
xStep = xRange / (1 << zDepth)
yStep = yRange / (1 << zDepth)
def z2x(z):
x = 0
for i in range(0, zDepth):
x += (z & (1 << i * 2)) >> i
return x
def z2y(z):
y = 0
for i in range(0, zDepth):
y += (z & (1 << (1 + i * 2))) >> (i + 1)
return y
def rectForZ(z, depth):
mask = (1 << (2*(zDepth - depth))) - 1
x = z2x(z & ~mask) * xStep + xStart
y = z2y(z & ~mask) * yStep + yStart
xSize = xRange / (1 << depth)
ySize = yRange / (1 << depth)
return QgsRectangle(x, y, x + xSize, y + ySize)
def tzIdToEnum(tzId):
if not tzId:
return 'Undefined'
return tzId.replace('/', '_').replace('-', '_')
# Layer specific configuration for things considered in the spatial index
class LayerConfig:
def readFeatureValue(self, feature):
return feature[self.featureKey]
def isValidFeature(self, feature):
return feature != None
def normalizeAndFilter(self, features):
for key in features:
if not self.isValidFeature(key):
del(features[key])
return features
def resolveConflicts(self, features):
out = []
for key in features:
out.append((key, features[key]));
n = functools.reduce(lambda n, f: n + f[1], out, 0)
if n > 0:
out = [(k, v/n) for (k, v) in out]
out = list(filter(lambda x: x[1] > featureAreaRatioThreshold, out))
out.sort(key = lambda x: x[1], reverse = True)
return out
class TzLayerConfig(LayerConfig):
def __init__(self):
self.layer = 'tzLayer'
self.featureKey = 'tzid'
def readFeatureValue(self, feature):
tzId = feature[self.featureKey]
if tzId in TZID_MAP:
return TZID_MAP[tzId]
return tzId
def resolveConflicts(self, features):
if len(features) > 1:
tzs = list(features.keys())
# check for conflicting timezones
tz = pytz.timezone(tzs[0])
if not all(self.isSameTimezone(tz, pytz.timezone(x)) for x in tzs[1:]):
return None
out = super().resolveConflicts(features)
return [out[0]]
return super().resolveConflicts(features)
def isSameTimezone(self, lhs, rhs):
try:
# hacky tz comparison, lacking access to the rules for comparing actual DST transition times
# TODO stabilize results and ensure we capture differences due to different week boundaries
dt = datetime.datetime.today().toordinal()
return all(lhs.utcoffset(datetime.datetime.fromordinal(dt + 30*x)) == rhs.utcoffset(datetime.datetime.fromordinal(dt + 30*x))
and lhs.tzname(datetime.datetime.fromordinal(dt + 30*x)) == rhs.tzname(datetime.datetime.fromordinal(dt + 30*x)) for x in range(0, 11))
except:
return False
class CountryLayerConfig(LayerConfig):
def __init__(self):
self.layer = 'countryLayer'
self.featureKey = 'ISO3166-1'
def readFeatureValue(self, feature):
code = feature[self.featureKey]
if code in ISO3166_1_MAP:
return ISO3166_1_MAP[code]
return code
def normalizeAndFilter(self, features):
if len(features) > 1:
# apply country disambiguation
for d in ISO3166_1_DISAMBIGUATION_MAP:
if d[0] in features and d[1] in features:
del(features[d[1]])
return super().normalizeAndFilter(features)
def resolveConflicts(self, features):
out = super().resolveConflicts(features)
if len(out) > 1:
return None
return out
class SubdivLayerConfig(LayerConfig):
def __init__(self):
self.layer = 'subdivLayer'
self.featureKey = 'ISO3166-2'
def resolveConflicts(self, features):
out = super().resolveConflicts(features)
if len(out) > 1:
return None
return out
#
# Parallelized spatial index computation of a single sub-tile
#
class SpatialIndexerSubTask(QgsTask):
def __init__(self, context, zStart, zStartDepth):
super().__init__('Compute spatial index sub-tile ' + str(zStart), QgsTask.CanCancel)
self.context = context
self.zStart = zStart
self.zStartDepth = zStartDepth
self.lastFeature = []
self.exception = None
self.result = []
self.layerConfig = [TzLayerConfig(), CountryLayerConfig(), SubdivLayerConfig()]
def run(self):
try:
self.computeTile(self.zStart, self.zStartDepth)
except Exception as e:
self.exception = e
QgsMessageLog.logMessage('Exception in task "{}"'.format(self.exception), LOG_CATEGORY, Qgis.Info)
return False
return True
def computeTile(self, zStart, depth):
if self.isCanceled() or depth < 1:
return
z = zStart
d = depth - 1
zIncrement = 1 << (2*d)
for i in range(0, 4):
# find features in the input vector layer inside our current tile
# we cannot take shortcuts here and avoid the expensive area computation
# as we do get spurious 0-area results for some large and disjoint polygons (FR, US, NZ, etc)
feature = []
featureCount = 0
for layerConfig in self.layerConfig:
l = {}
rect = rectForZ(z, zDepth - d)
rectGeo = QgsGeometry.fromRect(rect)
for f in self.context[layerConfig.layer].getFeatures(rect):
featureArea = f.geometry().intersection(rectGeo).area()
if featureArea > 0.0:
key = layerConfig.readFeatureValue(f)
if key in l:
l[key] += featureArea / rectGeo.area()
else:
l[key] = featureArea / rectGeo.area()
feature.append(layerConfig.normalizeAndFilter(l))
featureCount = max(featureCount, len(l))
# recurse on conflicts
if depth > 1 and featureCount > 1:
self.computeTile(z, d)
# leaf tile, process the result
else:
# try to clean up any remaining conflicts
for i in range(len(feature)):
feature[i] = self.layerConfig[i].resolveConflicts(feature[i])
self.resolveConflicts(feature)
# if there's a change to the previous value, propagate to the result output
if self.lastFeature != feature and feature != []:
self.result.append((z, feature))
self.lastFeature = feature
z += zIncrement
def resolveConflicts(self, feature):
# if we have a unique subdivision but no country, use the subdivision's country
# this happens in coastal regions when territorial waters overlap in the tile, but
# actual land boundaries don't. As we primarily care about correct result on land,
# we can ignore the overlap on water
if (not feature[1] or len(feature[1]) == 0) and feature[2] and len(feature[2]) == 1:
feature[1] = [(feature[2][0][0][:2], 1)]
# if subdivision country and country code conflict, discard the subdivision
# this is mainly a problem for French overseas territories, and there the country
# code is actually more useful
if feature[1] and len(feature[1]) == 1 and feature[2] and len(feature[2]) == 1 and feature[1][0][0] != feature[2][0][0][:2]:
feature[2] = None
def finished(self, result):
if not result and self.exception != None:
QgsMessageLog.logMessage('Task "{name}" Exception: {exception}'.format(name=self.description(), exception=self.exception), LOG_CATEGORY, Qgis.Critical)
raise self.exception
#
# Task for spawning the sub-tasks doing the actual work, and accumulating the result
#
class SpatialIndexerTask(QgsTask):
def __init__(self, context, loadLayersTask):
super().__init__('Compute spatial index', QgsTask.CanCancel)
self.context = context
self.tasks = []
self.conflictTiles = [0, 0, 0]
self.startTime = time.time()
self.propertyTable = []
startDepth = 4
startIncrement = 1 << (2 * (zDepth - startDepth))
for i in range(0, (1 << (2 * startDepth))):
task = SpatialIndexerSubTask(context, i * startIncrement, zDepth - startDepth)
self.addSubTask(task, [loadLayersTask], QgsTask.ParentDependsOnSubTask)
self.tasks.append(task)
def run(self):
try:
QgsMessageLog.logMessage('Aggregating results...', LOG_CATEGORY, Qgis.Info)
# compact the spatial index and prepare the property map
spatialIndex = []
propertyMap = {}
prevProperty = (None, None, None)
propertyMap[prevProperty] = 0
for task in self.tasks:
for (z,res) in task.result:
res = self.normalize(res);
tmp = []
for i in range(len(res)):
if res[i] == None or len(res[i]) == 0:
tmp.append(None)
else:
tmp.append(res[i][0][0])
if res[i] == None:
self.conflictTiles[i] += 1
prop = (tmp[0], tmp[1], tmp[2])
if prevProperty == prop:
continue
prevProperty = prop
spatialIndex.append((z, prop))
propertyMap[prop] = 0
for prop in propertyMap:
self.propertyTable.append(prop)
self.propertyTable.sort(key = lambda x: (x[0] if x[0] else "", x[1] if x[1] else "", x[2] if x[2] else ""))
for i in range(len(self.propertyTable)):
propertyMap[self.propertyTable[i]] = i
# write spatial index
out = open('../../data/spatial_index_data.cpp', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated spatial index generated using QGIS.
*/
#include "spatial_index_entry_p.h"
static constexpr const SpatialIndexEntry spatial_index[] = {
// clang-format off
""")
prevProperties = (None, None, None)
for (z, prop) in spatialIndex:
out.write(f" {{{z}, {propertyMap[prop]}}},\n")
out.write(" // clang-format on\n};\n")
out.close()
# write property table
out = open('../../data/spatial_index_properties.cpp', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated spatial index generated using QGIS.
*/
#include "spatial_index_property_p.h"
#include "timezone_names_p.h"
static constexpr const SpatialIndexProperty spatial_index_properties[] = {
""")
for p in self.propertyTable:
if not p[1] and not p[2]:
out.write(f" {{Tz::{tzIdToEnum(p[0])}}},\n")
elif not p[2]:
out.write(f" {{Tz::{tzIdToEnum(p[0])}, \"{p[1]}\"}},\n")
else:
out.write(f" {{Tz::{tzIdToEnum(p[0])}, \"{p[2]}\"}},\n")
out.write("};\n")
out.close()
# write zindex parameters
out = open('../../data/spatial_index_parameters_p.h', 'w')
out.write("""/*
* SPDX-License-Identifier: CC0-1.0
* SPDX-FileCopyrightText: none
*
* Autogenerated spatial index generated using QGIS.
*/
#include <cstdint>
""")
out.write(f"constexpr const float XStart = {xStart};\n")
out.write(f"constexpr const float XRange = {xRange};\n")
out.write(f"constexpr const float YStart = {yStart};\n")
out.write(f"constexpr const float YRange = {yRange};\n")
out.write(f"constexpr const uint8_t ZDepth = {zDepth};\n")
out.close()
return True
except Exception as e:
self.exception = e
QgsMessageLog.logMessage('Exception in task "{}"'.format(self.exception), LOG_CATEGORY, Qgis.Info)
return False
def normalize(self, feature):
# normalization we couldn't do in the sub tasks as they rely on results not available yet at that point
# fill in missing timezones from country/subdiv to tz maps
# this is needed for the same reason we do the subdivision to country back filling above, conflicts in
# territorial waters but a unique result on land
tz = None
if feature[2] and len(feature[2]) == 1:
subdivCode = feature[2][0][0]
subdivToTz = self.context['subdivisionToTimezoneMap']
if subdivCode in subdivToTz and len(subdivToTz[subdivCode]) == 1:
tz = list(subdivToTz[subdivCode])[0]
if not tz and feature[1] and len(feature[1]) == 1:
countryCode = feature[1][0][0]
countryToTz = self.context['countryToTimezoneMap']
if countryCode in countryToTz and len(countryToTz[countryCode]) == 1:
tz = list(countryToTz[countryCode])[0]
if tz:
feature[0] = [(tz, 1)]
return feature
def finished(self, result):
QgsMessageLog.logMessage('Finished task "{}"'.format(self.description()), LOG_CATEGORY, Qgis.Info)
tileCount = 1 << (2 * zDepth)
for i in range(len(self.conflictTiles)):
QgsMessageLog.logMessage(f" {self.conflictTiles[i] / tileCount} of feature {i} area is conflicted", LOG_CATEGORY, Qgis.Info)
QgsMessageLog.logMessage(f" collected {len(self.propertyTable)} distinct features", LOG_CATEGORY, Qgis.Info)
QgsMessageLog.logMessage(f" computation took {time.time() - self.startTime} seconds", LOG_CATEGORY, Qgis.Info)
if not result and self.exception != None:
QgsMessageLog.logMessage('Task "{name}" Exception: {exception}'.format(name=self.description(), exception=self.exception), LOG_CATEGORY, Qgis.Critical)
raise self.exception
@@ -0,0 +1,185 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: LGPL-2.0-or-later
from config import *
import io
import os
from qgis import *
from qgis.core import *
def tzIdToEnum(tzId):
return tzId.replace('/', '_').replace('-', '_')
def normalizedTz(tzId):
if tzId in TZID_MAP:
return TZID_MAP[tzId]
return tzId
def normalizedCountry(code):
if code in ISO3166_1_MAP:
return ISO3166_1_MAP[code]
return code
# Generate IANA timezone names string table
# This allows us to reference timezones by a uint16_t in other data tables
class TimezoneStringTableTask(QgsTask):
def __init__(self, context):
super().__init__('Generate timezone string table', QgsTask.CanCancel)
self.context = context
def run(self):
QgsMessageLog.logMessage('Generating timezone string table', LOG_CATEGORY, Qgis.Info)
tzLayer = self.context['tzLayer']
tzIds = set()
for tz in tzLayer.getFeatures():
tzIds.add(tz['tzid'])
tzIds = list(tzIds)
tzIds.sort()
offsets = [0]
out = open('../../data/timezone_name_table.cpp', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
static constexpr const char timezone_name_table[] =
""")
for tzId in tzIds:
out.write(f" \"{tzId}\\0\"\n")
offsets.append(offsets[-1] + len(tzId) + 1)
out.seek(out.tell() - 1, io.SEEK_SET) # to make clang-format happy
out.write(";\n")
out.close()
out = open('../../data/timezone_names_p.h', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#ifndef TIMEZONE_NAMES_P_H
#define TIMEZONE_NAMES_P_H
#include <cstdint>
enum Tz : uint16_t {
""")
for i in range(len(tzIds)):
out.write(f" {tzIdToEnum(tzIds[i])} = {offsets[i]},\n")
out.write(f" Undefined = {offsets[-1] - 1},\n") # points to the last null byte
out.write("};\n\n#endif\n")
out.close()
return True
# Computes country/country subdivision to timezone mapping
class RegionToTimezoneMapTask(QgsTask):
def __init__(self, context):
super().__init__('Computing region to timezone mapping', QgsTask.CanCancel)
self.context = context
def run(self):
QgsMessageLog.logMessage('Computing region to timezone mapping', LOG_CATEGORY, Qgis.Info)
countryLayer = self.context['countryLayer']
tzLayer = self.context['tzLayer']
countryToTz = {}
for country in countryLayer.getFeatures():
countryCode = country['ISO3166-1']
if not countryCode in countryToTz:
countryToTz[countryCode] = set()
countryGeom = country.geometry()
countryArea = countryGeom.area()
for tz in tzLayer.getFeatures():
tzId = normalizedTz(tz['tzId'])
if tz.geometry().intersects(countryGeom):
# filter out intersection noise along the boundaries
area = tz.geometry().intersection(countryGeom).area()
tzAreaRatio = area / tz.geometry().area()
countryAreaRatio = area / countryArea
if tzAreaRatio > 0.01 or countryAreaRatio > 0.1:
countryToTz[countryCode].add(tzId)
out = open('../../data/country_timezone_map.cpp', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#include "isocodes_p.h"
#include "mapentry_p.h"
#include "timezone_names_p.h"
static constexpr const MapEntry<uint16_t> country_timezone_map[] = {
""")
countries = list(countryToTz)
countries.sort()
for country in countries:
if len(countryToTz[country]) == 1:
out.write(f" {{IsoCodes::alpha2CodeToKey(\"{country}\"), Tz::{tzIdToEnum(list(countryToTz[country])[0])}}},\n")
out.write("};\n")
out.close()
# for countries with more than one tz, match against subdivisions
subdivToTz = {}
subdivLayer = self.context['subdivLayer']
tzLayer = self.context['tzLayer']
for subdiv in subdivLayer.getFeatures():
code = subdiv['ISO3166-2']
country = code[:2]
if len(countryToTz[country]) <= 1:
continue
if not code in subdivToTz:
subdivToTz[code] = {}
subdivGeom = subdiv.geometry()
subdivArea = subdivGeom.area()
for tz in tzLayer.getFeatures():
tzId = normalizedTz(tz['tzId'])
if tz.geometry().intersects(subdivGeom):
# filter out intersection noise along the boundaries
area = tz.geometry().intersection(subdivGeom).area()
tzAreaRatio = area / tz.geometry().area()
subdivAreaRatio = area / subdivArea
if tzAreaRatio > 0.01 or subdivAreaRatio > 0.1:
if not tzId in subdivToTz[code]:
subdivToTz[code][tzId] = area
else:
subdivToTz[code][tzId] += area
out = open('../../data/subdivision_timezone_map.cpp', 'w')
out.write("""/*
* SPDX-License-Identifier: ODbL-1.0
* SPDX-FileCopyrightText: OpenStreetMap contributors
*
* Autogenerated using QGIS - do not edit!
*/
#include "isocodes_p.h"
#include "mapentry_p.h"
#include "timezone_names_p.h"
static constexpr const MapEntry<uint32_t> subdivision_timezone_map[] = {
""")
subdivs = list(subdivToTz)
subdivs.sort()
for subdiv in subdivs:
# sort by area, biggest one first
tzs = list(subdivToTz[subdiv])
tzs.sort(key = lambda x: subdivToTz[subdiv][x], reverse = True)
for tz in tzs:
out.write(f" {{IsoCodes::subdivisionCodeToKey(\"{subdiv}\"), Tz::{tzIdToEnum(tz)}}},\n")
if len(subdivToTz[subdiv]) == 0:
out.write(f" // {subdiv}\n")
out.write("};\n")
out.close()
self.context['countryToTimezoneMap'] = countryToTz
self.context['subdivisionToTimezoneMap'] = subdivToTz
return True
@@ -0,0 +1,125 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: LGPL-2.0-or-later
# data sources
TZDATA_VERSION = '2020d'
TZDATA_URL = f"https://github.com/evansiroky/timezone-boundary-builder/releases/download/{TZDATA_VERSION}/timezones.shapefile.zip"
ISO3166_1_VERSION = '2021-08-16'
ISO3166_1_URL = f"https://volkerkrause.eu/~vkrause/iso3166-boundaries/iso3166-1-boundaries.shp-{ISO3166_1_VERSION}.zip"
ISO3166_2_VERSION = ISO3166_1_VERSION
ISO3166_2_URL = f"https://volkerkrause.eu/~vkrause/iso3166-boundaries/iso3166-2-boundaries.shp-{ISO3166_2_VERSION}.zip"
# ISO 3166-1 code mappings
# This is for codes that should be unconditionally replaced, which is mainly useful
# for certain historical or political corner-cases or "non-countries" like Antarctica
ISO3166_1_MAP = {
'AQ': None,
'AX': 'FI',
'SJ': 'NO',
'UM': 'US'
}
# ISO 3166-1 overlap disambiguation
# This is mainly relevant for oversees territories that both have their own code
# as well that of the country they belong to
ISO3166_1_DISAMBIGUATION_MAP = {
( 'AS', 'US' ),
( 'AW', 'NL' ),
( 'BL', 'FR' ),
( 'BQ', 'NL' ),
( 'BT', 'CN' ),
( 'CC', 'AU' ),
( 'CW', 'NL' ),
( 'CX', 'AU' ),
( 'GF', 'FR' ),
( 'GP', 'FR' ),
( 'GU', 'US' ),
( 'HK', 'CN' ),
( 'HT', 'US' ),
( 'MF', 'FR' ),
( 'MO', 'CN' ),
( 'MP', 'US' ),
( 'MQ', 'FR' ),
( 'NC', 'FR' ),
( 'NF', 'AU' ),
( 'PF', 'FR' ),
( 'PM', 'FR' ),
( 'PR', 'US' ),
( 'RE', 'FR' ),
( 'SX', 'NL' ),
( 'TF', 'FR' ),
( 'VI', 'US' ),
( 'WF', 'FR' ),
( 'YT', 'FR' )
}
# ISO 3166-2 filter configuration
# most of those are sub-subdivisions
ISO3166_2_FILTER = [
{ 'country': 'BD', 'admin_level': 5 },
{ 'country': 'BE', 'admin_level': 6 },
{ 'country': 'BF', 'admin_level': 5 },
{ 'country': 'CZ', 'admin_level': 7 },
{ 'country': 'ES', 'admin_level': 6 },
{ 'country': 'FR', 'admin_level': 5 },
{ 'country': 'FR', 'admin_level': 6 },
{ 'country': 'GB', 'admin_level': 6 },
{ 'country': 'GB', 'admin_level': 8 },
{ 'country': 'GN', 'admin_level': 6 },
{ 'country': 'GQ', 'admin_level': 4 },
{ 'country': 'GW', 'admin_level': 4 },
{ 'country': 'IE', 'admin_level': 6 },
{ 'country': 'IE', 'admin_level': 7 },
{ 'country': 'IT', 'admin_level': 6 },
{ 'country': 'LK', 'admin_level': 5 },
{ 'country': 'LT', 'admin_level': 5 },
{ 'country': 'MA', 'admin_level': 5 },
{ 'country': 'MW', 'admin_level': 4 },
{ 'country': 'NP', 'admin_level': 4 },
{ 'country': 'NP', 'admin_level': 5 },
{ 'country': 'PH', 'admin_level': 4 },
{ 'country': 'UG', 'admin_level': 4 }
]
# Timezone mapping
# use this to replace timezone ids with an equivalent
# this is mainly useful for filtering out timezones of limited practical or merely historical relevance,
# trading historical accuracy for practical usability for current and future date/time values
TZID_MAP = {
'America/Argentina/Catamarca': 'America/Argentina/Buenos_Aires',
'America/Argentina/Cordoba': 'America/Argentina/Buenos_Aires',
'America/Argentina/Jujuy': 'America/Argentina/Buenos_Aires',
'America/Argentina/La_Rioja': 'America/Argentina/Buenos_Aires',
'America/Argentina/Mendoza': 'America/Argentina/Buenos_Aires',
'America/Argentina/Rio_Gallegos': 'America/Argentina/Buenos_Aires',
'America/Argentina/Salta': 'America/Argentina/Buenos_Aires',
'America/Argentina/San_Juan': 'America/Argentina/Buenos_Aires',
'America/Argentina/San_Luis': 'America/Argentina/Buenos_Aires',
'America/Argentina/Tucuman': 'America/Argentina/Buenos_Aires',
'America/Argentina/Ushuaia': 'America/Argentina/Buenos_Aires',
'America/Nipigon': 'America/Toronto',
'Arctic/Longyearbyen': 'Europe/Oslo',
'Asia/Famagusta': 'Asia/Nicosia',
'Asia/Kuching': 'Asia/Kuala_Lumpur',
'Europe/Busingen': 'Europe/Berlin',
'Europe/Mariehamn': 'Europe/Helsinki'
}
#
# parameters for the spatial index
#
featureAreaRatioThreshold = 0.02 # 1% at zDepth 11 is ~150m
zDepth = 11 # minimum tile size is 1/(2^zdepth), amount of bits needed to store z index is 2*zDepth
# z-order curve coverage parameters
xStart = -180
xRange = 360
# cut out artic regions (starting at 60°S and 80°N), that saves about 23% z-order curve coverage which we
# can better use to increase precision in more relevant areas
yStart = -60
yRange = 140
# constants
LOG_CATEGORY = 'KI18n Data Generator'
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: LGPL-2.0-or-later
import os
import qgis
from LayerTasks import *
from SpatialIndexTasks import *
from TimezoneTableTasks import *
from config import *
class MainTask(QgsTask):
def __init__(self, context):
super().__init__('Generating geographic data for KI18N', QgsTask.CanCancel)
self.context = context
self.loadLayersTask = LoadLayersTask(context)
self.addSubTask(self.loadLayersTask, [], QgsTask.ParentDependsOnSubTask)
self.tzStringTableTask = TimezoneStringTableTask(context)
self.addSubTask(self.tzStringTableTask, [self.loadLayersTask], QgsTask.ParentDependsOnSubTask)
self.regionToTzMapTask = RegionToTimezoneMapTask(context)
self.addSubTask(self.regionToTzMapTask, [self.loadLayersTask], QgsTask.ParentDependsOnSubTask)
self.spatialIndexTask = SpatialIndexerTask(context, self.loadLayersTask)
self.addSubTask(self.spatialIndexTask, [self.loadLayersTask, self.regionToTzMapTask], QgsTask.ParentDependsOnSubTask)
def run(self):
QgsMessageLog.logMessage('Generation completed.', LOG_CATEGORY, Qgis.Info)
return True
# main
os.chdir(os.path.join(os.path.dirname(QgsProject.instance().fileName()), 'data'))
context = {}
task = MainTask(context)
QgsApplication.taskManager().addTask(task)
@@ -0,0 +1,115 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.18.0-Zürich">
<homePath path=""/>
<title></title>
<autotransaction active="0"/>
<evaluateDefaultValues active="0"/>
<trust active="0"/>
<projectCrs>
<spatialrefsys>
<wkt>GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</projectCrs>
<mapcanvas annotationsVisible="1" name="theMapCanvas">
<units>degrees</units>
<extent>
<xmin>-1</xmin>
<ymin>-1</ymin>
<xmax>1</xmax>
<ymax>1</ymax>
</extent>
<rotation>0</rotation>
<destinationsrs>
<spatialrefsys>
<wkt>GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</destinationsrs>
<rendermaptile>0</rendermaptile>
<expressionContextScope/>
</mapcanvas>
<projectModels/>
<legend updateDrawingOrder="true"/>
<mapViewDocks/>
<mapViewDocks3D/>
<main-annotation-layer autoRefreshTime="0" refreshOnNotifyEnabled="0" type="annotation" autoRefreshEnabled="0" refreshOnNotifyMessage="">
<id>Annotations_41dc69d9_cc28_4177_a7a0_439e8e5129dc</id>
<datasource></datasource>
<keywordList>
<value></value>
</keywordList>
<layername>Annotations</layername>
<srs>
<spatialrefsys>
<wkt></wkt>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<wkt></wkt>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</crs>
<extent/>
</resourceMetadata>
<items/>
<layerOpacity>1</layerOpacity>
<blendMode>0</blendMode>
</main-annotation-layer>
<ProjectViewSettings UseProjectScales="0">
<Scales/>
<DefaultViewExtent ymin="-1" xmin="-3.3624454148471612" xmax="3.3624454148471612" ymax="1">
<spatialrefsys>
<wkt>GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</DefaultViewExtent>
</ProjectViewSettings>
</qgis>
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,55 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "data/spatial_index_data.cpp"
#include "data/spatial_index_parameters_p.h"
#include "data/spatial_index_properties.cpp"
#include "spatial_index_p.h"
#include <cmath>
#include <cstdint>
constexpr const float XEnd = XStart + XRange;
constexpr const float YEnd = YStart + YRange;
// verify the null entry is first in the property table
static_assert(spatial_index_properties[0].m_tz == Tz::Undefined);
static_assert(spatial_index_properties[0].m_subdiv == 0);
// z index conversions
constexpr uint32_t latlonToZ(float lat, float lon)
{
const uint32_t x = ((lon - XStart) / XRange) * (1 << ZDepth);
const uint32_t y = ((lat - YStart) / YRange) * (1 << ZDepth);
uint32_t z = 0;
for (int i = ZDepth - 1; i >= 0; --i) {
z <<= 1;
z += (y & (1 << i)) ? 1 : 0;
z <<= 1;
z += (x & (1 << i)) ? 1 : 0;
}
return z;
}
// "unit tests" for the z index conversion
static_assert(latlonToZ(YStart, XStart) == 0);
static_assert(latlonToZ(YEnd - 1.0f / (1 << ZDepth), XEnd - 1.0f / (1 << ZDepth)) == (1 << (2 * ZDepth)) - 1);
static_assert(latlonToZ(YStart + YRange / 2.0f, 0.0f) == ((1 << (2 * ZDepth - 1)) | (1 << (2 * ZDepth - 2))));
SpatialIndexProperty SpatialIndex::lookup(float lat, float lon)
{
if (std::isnan(lat) || std::isnan(lon) || lon < XStart || lon >= XEnd || lat < YStart || lat >= YEnd) {
return spatial_index_properties[0];
}
const auto z = latlonToZ(lat, lon);
const auto it = std::upper_bound(std::begin(spatial_index), std::end(spatial_index), z);
if (it == std::begin(spatial_index)) {
return spatial_index_properties[0];
}
return spatial_index_properties[(*std::prev(it)).propertyIndex()];
}
@@ -0,0 +1,16 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "spatial_index_entry_p.h"
// unit tests for the spatial index entry template
static_assert(sizeof(SpatialIndexEntry) == 5);
static_assert(SpatialIndexEntry(0, 1023).z() == 0);
static_assert(SpatialIndexEntry(0, 1023).propertyIndex() == 1023);
static_assert(SpatialIndexEntry(4194303, 1023).z() == 4194303);
static_assert(SpatialIndexEntry(4194303, 1023).propertyIndex() == 1023);
@@ -0,0 +1,67 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef SPATIAL_INDEX_ENTRY_P_H
#define SPATIAL_INDEX_ENTRY_P_H
#include <cstdint>
#pragma pack(push)
#pragma pack(1)
/** Entry in the spatial index.
* This is essentially just a pair of numbers, the first being the position
* on the z-order curve and defines the order, the second being the index into the
* property table.
*
* These entries exist in very large quantities, so compact storage is important.
*/
class SpatialIndexEntry
{
public:
inline constexpr SpatialIndexEntry(uint32_t z, uint32_t propertyIdx)
: m_z(z)
, m_unused(0)
, m_propHigh(propertyIdx >> 8)
, m_propLow(propertyIdx)
{
}
inline constexpr uint32_t z() const
{
return m_z;
}
inline constexpr uint32_t propertyIndex() const
{
return m_propHigh << 8 | m_propLow;
}
private:
uint32_t m_z : 22;
[[maybe_unused]] uint32_t m_unused : 6;
uint32_t m_propHigh : 4;
uint8_t m_propLow;
};
#pragma pack(pop)
inline constexpr bool operator<(SpatialIndexEntry lhs, SpatialIndexEntry rhs)
{
return lhs.z() < rhs.z();
}
inline constexpr bool operator<(SpatialIndexEntry lhs, uint32_t rhs)
{
return lhs.z() < rhs;
}
inline constexpr bool operator<(uint32_t lhs, SpatialIndexEntry rhs)
{
return lhs < rhs.z();
}
#endif
@@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef SPATIALINDEX_H
#define SPATIALINDEX_H
#include "spatial_index_property_p.h"
/** Spatial index lookup functions . */
namespace SpatialIndex
{
SpatialIndexProperty lookup(float lat, float lon);
}
#endif // SPATIALINDEX_H
@@ -0,0 +1,22 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "spatial_index_property_p.h"
// unit tests for the spatial index property table type
static_assert(sizeof(SpatialIndexProperty) == 6);
// timezone only
static_assert(SpatialIndexProperty(Tz::Pacific_Auckland).m_tz == Tz::Pacific_Auckland);
static_assert(SpatialIndexProperty(Tz::Pacific_Auckland).m_subdiv == 0);
// country only
static_assert(SpatialIndexProperty(Tz::Europe_Zurich, "CH").m_subdiv > 0);
static_assert((SpatialIndexProperty(Tz::Europe_Zurich, "CH").m_subdiv & 0xffff) == 0);
// subdivision
static_assert(SpatialIndexProperty(Tz::Europe_Paris, "FR-IDF").m_subdiv > 0);
static_assert((SpatialIndexProperty(Tz::Europe_Paris, "FR-IDF").m_subdiv & 0xffff) > 0);
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef SPATIAL_INDEX_PROPERTY_P_H
#define SPATIAL_INDEX_PROPERTY_P_H
#include "data/timezone_names_p.h"
#include "isocodes_p.h"
#include <cstdint>
#pragma pack(push)
#pragma pack(1)
/** Entry in the spatial index property table.
* That is, this is a set of properties (timezone, country, country subdivision)
* associated with a tile in the spatial index, optimized for compact storage.
*/
class SpatialIndexProperty
{
public:
template<std::size_t N>
inline constexpr SpatialIndexProperty(Tz tz, const char (&code)[N])
: m_tz(tz)
, m_subdiv(N == 3 ? (IsoCodes::alpha2CodeToKey(code, 2) << 16) : IsoCodes::subdivisionCodeToKey(code, N - 1))
{
}
inline constexpr SpatialIndexProperty(Tz tz)
: m_tz(tz)
, m_subdiv(0)
{
}
Tz m_tz;
uint32_t m_subdiv;
};
#pragma pack(pop)
#endif
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "timezonedata_p.h"
#include "data/country_timezone_map.cpp"
#include "data/subdivision_timezone_map.cpp"
#include "data/timezone_name_table.cpp"
#include <cassert>
static_assert(isSortedLookupTable(country_timezone_map));
static_assert(isSortedLookupTable(subdivision_timezone_map));
const char *TimezoneData::ianaIdLookup(uint16_t offset)
{
assert(offset < sizeof(timezone_name_table));
return timezone_name_table + offset;
}
const MapEntry<uint16_t> *TimezoneData::countryTimezoneMapBegin()
{
return std::begin(country_timezone_map);
}
const MapEntry<uint16_t> *TimezoneData::countryTimezoneMapEnd()
{
return std::end(country_timezone_map);
}
const MapEntry<uint32_t> *TimezoneData::subdivisionTimezoneMapBegin()
{
return std::begin(subdivision_timezone_map);
}
const MapEntry<uint32_t> *TimezoneData::subdivisionTimezoneMapEnd()
{
return std::end(subdivision_timezone_map);
}
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef TIMEZONEDATA_P_H
#define TIMEZONEDATA_P_H
#include "mapentry_p.h"
#include <cstdint>
/** Utility functions to deal with the compiled-in timezone data. */
namespace TimezoneData
{
const char *ianaIdLookup(uint16_t offset);
const MapEntry<uint16_t> *countryTimezoneMapBegin();
const MapEntry<uint16_t> *countryTimezoneMapEnd();
const MapEntry<uint32_t> *subdivisionTimezoneMapBegin();
const MapEntry<uint32_t> *subdivisionTimezoneMapEnd();
}
#endif // TIMEZONEDATA_P_H