mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
Add framework for translating ZNC to different languages
This commit is contained in:
@@ -32,6 +32,10 @@ function(znc_add_executable name)
|
||||
add_executable("${name}" ${ARGN})
|
||||
set(_all_targets "${_all_targets};${name}" CACHE INTERNAL "")
|
||||
endfunction()
|
||||
function(znc_add_custom_target name)
|
||||
add_custom_target("${name}" ${ARGN})
|
||||
set(_all_targets "${_all_targets};${name}" CACHE INTERNAL "")
|
||||
endfunction()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
|
||||
@@ -175,6 +179,24 @@ if(WANT_TCL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
tristate_option(I18N "Native language support (i18n)")
|
||||
if(WANT_I18N)
|
||||
find_package(Boost ${TRISTATE_I18N_REQUIRED} COMPONENTS locale)
|
||||
find_package(Gettext ${TRISTATE_I18N_REQUIRED})
|
||||
endif()
|
||||
if(Boost_LOCALE_FOUND AND GETTEXT_MSGFMT_EXECUTABLE)
|
||||
set(HAVE_I18N true)
|
||||
else()
|
||||
set(HAVE_I18N false)
|
||||
endif()
|
||||
|
||||
if(HAVE_I18N AND GETTEXT_MSGMERGE_EXECUTABLE)
|
||||
find_program(XGETTEXT_EXECUTABLE xgettext)
|
||||
if(XGETTEXT_EXECUTABLE)
|
||||
add_custom_target(translation)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# poll() is broken on Mac OS, it fails with POLLNVAL for pipe()s.
|
||||
if(APPLE)
|
||||
set(CSOCK_USE_POLL false)
|
||||
@@ -321,6 +343,7 @@ summary_line("Tcl " "${TCL_FOUND}")
|
||||
summary_line("Cyrus " "${CYRUS_FOUND}")
|
||||
summary_line("Charset " "${ICU_FOUND}")
|
||||
summary_line("Zlib " "${ZLIB_FOUND}")
|
||||
summary_line("i18n " "${HAVE_I18N}")
|
||||
|
||||
include(render_framed_multiline)
|
||||
render_framed_multiline("${summary_lines}")
|
||||
|
||||
@@ -53,7 +53,7 @@ LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp IRCNetwork.cpp User.cpp IRCSock.c
|
||||
Client.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp MD5.cpp Buffer.cpp Utils.cpp \
|
||||
FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp \
|
||||
WebModules.cpp Listener.cpp Config.cpp ZNCDebug.cpp Threads.cpp version.cpp Query.cpp \
|
||||
SSLVerifyHost.cpp Message.cpp
|
||||
SSLVerifyHost.cpp Message.cpp Translation.cpp
|
||||
LIB_SRCS := $(addprefix src/,$(LIB_SRCS))
|
||||
BIN_SRCS := src/main.cpp
|
||||
LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS))
|
||||
|
||||
75
cmake/translation.cmake
Normal file
75
cmake/translation.cmake
Normal file
@@ -0,0 +1,75 @@
|
||||
#
|
||||
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
include(CMakeParseArguments)
|
||||
function(translation)
|
||||
cmake_parse_arguments(arg "" "FULL;SHORT" "SOURCES;TMPLDIRS" ${ARGN})
|
||||
set(short "${arg_SHORT}")
|
||||
file(GLOB all_po "${short}.*.po")
|
||||
|
||||
if(XGETTEXT_EXECUTABLE)
|
||||
set(params)
|
||||
foreach(i ${arg_SOURCES})
|
||||
list(APPEND params "--explicit_sources=${i}")
|
||||
endforeach()
|
||||
foreach(i ${arg_TMPLDIRS})
|
||||
list(APPEND params "--tmpl_dirs=${i}")
|
||||
endforeach()
|
||||
add_custom_target("translation_${short}"
|
||||
COMMAND "${PROJECT_SOURCE_DIR}/translation_pot.py"
|
||||
"--include_dir=${CMAKE_CURRENT_SOURCE_DIR}/.."
|
||||
"--strip_prefix=${PROJECT_SOURCE_DIR}/"
|
||||
"--tmp_prefix=${CMAKE_CURRENT_BINARY_DIR}/${short}"
|
||||
"--output=${CMAKE_CURRENT_SOURCE_DIR}/${short}.pot"
|
||||
${params}
|
||||
VERBATIM)
|
||||
foreach(one_po ${all_po})
|
||||
add_custom_command(TARGET "translation_${short}" POST_BUILD
|
||||
COMMAND "${GETTEXT_MSGMERGE_EXECUTABLE}"
|
||||
--update --quiet --backup=none "${one_po}" "${short}.pot"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
VERBATIM)
|
||||
endforeach()
|
||||
add_dependencies(translation "translation_${short}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${short}.pot")
|
||||
return()
|
||||
endif()
|
||||
|
||||
znc_add_custom_target("po_${short}")
|
||||
foreach(one_po ${all_po})
|
||||
get_filename_component(longext "${one_po}" EXT)
|
||||
if(NOT longext MATCHES "^\\.([a-zA-Z_]+)\\.po$")
|
||||
message(WARNING "Unrecognized translation file ${one_po}")
|
||||
continue()
|
||||
endif()
|
||||
set(lang "${CMAKE_MATCH_1}")
|
||||
|
||||
add_custom_command(OUTPUT "${short}.${lang}.gmo"
|
||||
COMMAND "${GETTEXT_MSGFMT_EXECUTABLE}"
|
||||
-D "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
-o "${short}.${lang}.gmo"
|
||||
"${short}.${lang}.po"
|
||||
DEPENDS "${short}.${lang}.po"
|
||||
VERBATIM)
|
||||
add_custom_target("po_${short}_${lang}" DEPENDS "${short}.${lang}.gmo")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${short}.${lang}.gmo"
|
||||
DESTINATION "${CMAKE_INSTALL_LOCALEDIR}/${lang}/LC_MESSAGES"
|
||||
RENAME "${arg_FULL}.mo")
|
||||
add_dependencies("po_${short}" "po_${short}_${lang}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
54
cmake/translation_tmpl.py
Executable file
54
cmake/translation_tmpl.py
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Extract translateable strings from .tmpl files')
|
||||
parser.add_argument('--directory', action='store')
|
||||
parser.add_argument('--output', action='store')
|
||||
args = parser.parse_args()
|
||||
|
||||
pattern = re.compile(r'<\?\s*(?:FORMAT|(PLURAL))\s+(?:CTX="([^"]+?)"\s+)?"([^"]+?)"(?(1)\s+"([^"]+?)"|).*?\?>')
|
||||
|
||||
result = []
|
||||
|
||||
for fname in glob.iglob(args.directory + '/*.tmpl'):
|
||||
fbase = os.path.basename(fname)
|
||||
with open(fname) as f:
|
||||
for linenum, line in enumerate(f):
|
||||
for x in pattern.finditer(line):
|
||||
text, plural, context = x.group(3), x.group(4), x.group(2)
|
||||
result.append('#: {}:{}'.format(fbase, linenum + 1))
|
||||
if context:
|
||||
result.append('msgctxt "{}"'.format(context))
|
||||
result.append('msgid "{}"'.format(text))
|
||||
if plural:
|
||||
result.append('msgid_plural "{}"'.format(plural))
|
||||
result.append('msgstr[0] ""')
|
||||
result.append('msgstr[1] ""')
|
||||
else:
|
||||
result.append('msgstr ""')
|
||||
result.append('')
|
||||
|
||||
if result:
|
||||
with open(args.output, 'w') as f:
|
||||
for line in result:
|
||||
print(line, file=f)
|
||||
@@ -82,6 +82,7 @@ tristate('swig')
|
||||
tristate('cyrus')
|
||||
tristate('charset', 'ICU')
|
||||
tristate('tcl')
|
||||
tristate('i18n')
|
||||
|
||||
class HandlePython(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <znc/Threads.h>
|
||||
#include <znc/Message.h>
|
||||
#include <znc/main.h>
|
||||
#include <znc/Translation.h>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
@@ -58,20 +59,24 @@ class CModInfo;
|
||||
#define ZNC_EXPORT_LIB_EXPORT
|
||||
#endif
|
||||
|
||||
#define MODCOMMONDEFS(CLASS, DESCRIPTION, TYPE) \
|
||||
extern "C" { \
|
||||
ZNC_EXPORT_LIB_EXPORT bool ZNCModInfo(double dCoreVersion, \
|
||||
CModInfo& Info); \
|
||||
ZNC_EXPORT_LIB_EXPORT bool ZNCModInfo(double dCoreVersion, \
|
||||
CModInfo& Info) { \
|
||||
if (dCoreVersion != VERSION) return false; \
|
||||
Info.SetDescription(DESCRIPTION); \
|
||||
Info.SetDefaultType(TYPE); \
|
||||
Info.AddType(TYPE); \
|
||||
Info.SetLoader(TModLoad<CLASS>); \
|
||||
TModInfo<CLASS>(Info); \
|
||||
return true; \
|
||||
} \
|
||||
#define MODCOMMONDEFS(CLASS, DESCRIPTION, TYPE) \
|
||||
extern "C" { \
|
||||
ZNC_EXPORT_LIB_EXPORT bool ZNCModInfo(double dCoreVersion, \
|
||||
CModInfo& Info); \
|
||||
ZNC_EXPORT_LIB_EXPORT bool ZNCModInfo(double dCoreVersion, \
|
||||
CModInfo& Info) { \
|
||||
if (dCoreVersion != VERSION) return false; \
|
||||
auto t = [&](const CString& sEnglish, const CString& sContext = "") { \
|
||||
return sEnglish.empty() ? "" : Info.t(sEnglish, sContext); \
|
||||
}; \
|
||||
t(CString()); /* Don't warn about unused t */ \
|
||||
Info.SetDescription(DESCRIPTION); \
|
||||
Info.SetDefaultType(TYPE); \
|
||||
Info.AddType(TYPE); \
|
||||
Info.SetLoader(TModLoad<CLASS>); \
|
||||
TModInfo<CLASS>(Info); \
|
||||
return true; \
|
||||
} \
|
||||
}
|
||||
|
||||
/** Instead of writing a constructor, you should call this macro. It accepts all
|
||||
@@ -273,6 +278,9 @@ class CModInfo {
|
||||
void SetLoader(ModLoader fLoader) { m_fLoader = fLoader; }
|
||||
void SetDefaultType(EModuleType eType) { m_eDefaultType = eType; }
|
||||
// !Setters
|
||||
|
||||
CString t(const CString& sEnglish, const CString& sContext = "") const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::set<EModuleType> m_seType;
|
||||
@@ -316,16 +324,18 @@ class CModCommand {
|
||||
const CString& sArgs, const CString& sDesc);
|
||||
CModCommand(const CString& sCmd, CmdFunc func, const CString& sArgs,
|
||||
const CString& sDesc);
|
||||
CModCommand(const CString& sCmd, CmdFunc func, const CString& sArgs,
|
||||
const CDelayedTranslation& dDesc);
|
||||
|
||||
/** Copy constructor, needed so that this can be saved in a std::map.
|
||||
* @param other Object to copy from.
|
||||
*/
|
||||
CModCommand(const CModCommand& other);
|
||||
CModCommand(const CModCommand& other) = default;
|
||||
|
||||
/** Assignment operator, needed so that this can be saved in a std::map.
|
||||
* @param other Object to copy from.
|
||||
*/
|
||||
CModCommand& operator=(const CModCommand& other);
|
||||
CModCommand& operator=(const CModCommand& other) = default;
|
||||
|
||||
/** Initialize a CTable so that it can be used with AddHelp().
|
||||
* @param Table The instance of CTable to initialize.
|
||||
@@ -341,7 +351,7 @@ class CModCommand {
|
||||
const CString& GetCommand() const { return m_sCmd; }
|
||||
CmdFunc GetFunction() const { return m_pFunc; }
|
||||
const CString& GetArgs() const { return m_sArgs; }
|
||||
const CString& GetDescription() const { return m_sDesc; }
|
||||
CString GetDescription() const;
|
||||
|
||||
void Call(const CString& sLine) const { m_pFunc(sLine); }
|
||||
|
||||
@@ -350,6 +360,8 @@ class CModCommand {
|
||||
CmdFunc m_pFunc;
|
||||
CString m_sArgs;
|
||||
CString m_sDesc;
|
||||
CDelayedTranslation m_dDesc;
|
||||
bool m_bTranslating;
|
||||
};
|
||||
|
||||
/** The base class for your own ZNC modules.
|
||||
@@ -1093,6 +1105,10 @@ class CModule {
|
||||
bool AddCommand(const CString& sCmd, const CString& sArgs,
|
||||
const CString& sDesc,
|
||||
std::function<void(const CString& sLine)> func);
|
||||
/// @return True if the command was successfully added.
|
||||
bool AddCommand(const CString& sCmd, const CString& sArgs,
|
||||
const CDelayedTranslation& dDesc,
|
||||
std::function<void(const CString& sLine)> func);
|
||||
/// @return True if the command was successfully removed.
|
||||
bool RemCommand(const CString& sCmd);
|
||||
/// @return The CModCommand instance or nullptr if none was found.
|
||||
@@ -1267,6 +1283,17 @@ class CModule {
|
||||
CModInfo::EModuleType eType);
|
||||
// !Global Modules
|
||||
|
||||
#ifndef SWIG
|
||||
// Translation
|
||||
CString t(const CString& sEnglish, const CString& sContext = "") const;
|
||||
CInlineFormatMessage f(const CString& sEnglish,
|
||||
const CString& sContext = "") const;
|
||||
CInlineFormatMessage p(const CString& sEnglish, const CString& sEnglishes,
|
||||
int iNum, const CString& sContext = "") const;
|
||||
CDelayedTranslation d(const CString& sEnglish,
|
||||
const CString& sContext = "") const;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
CModInfo::EModuleType m_eType;
|
||||
CString m_sDescription;
|
||||
@@ -1285,6 +1312,7 @@ class CModule {
|
||||
CString m_sSavePath;
|
||||
CString m_sArgs;
|
||||
CString m_sModPath;
|
||||
CTranslationDomainRefHolder m_Translation;
|
||||
|
||||
private:
|
||||
MCString
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
#include <znc/zncconfig.h>
|
||||
#include <znc/Csocket.h>
|
||||
#include <znc/Threads.h>
|
||||
#include <znc/Translation.h>
|
||||
|
||||
class CModule;
|
||||
|
||||
class CZNCSock : public Csock {
|
||||
class CZNCSock : public Csock, public CCoreTranslationMixin {
|
||||
public:
|
||||
CZNCSock(int timeout = 60);
|
||||
CZNCSock(const CString& sHost, u_short port, int timeout = 60);
|
||||
@@ -267,6 +268,18 @@ class CSocket : public CZNCSock {
|
||||
// Getters
|
||||
CModule* GetModule() const;
|
||||
// !Getters
|
||||
|
||||
#ifndef SWIG
|
||||
// Translation. As opposed to CCoreTranslationMixin, this one uses module.mo
|
||||
CString t(const CString& sEnglish, const CString& sContext = "") const;
|
||||
CInlineFormatMessage f(const CString& sEnglish,
|
||||
const CString& sContext = "") const;
|
||||
CInlineFormatMessage p(const CString& sEnglish, const CString& sEnglishes,
|
||||
int iNum, const CString& sContext) const;
|
||||
CDelayedTranslation d(const CString& sEnglish,
|
||||
const CString& sContext = "") const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
protected:
|
||||
CModule*
|
||||
|
||||
91
include/znc/Translation.h
Normal file
91
include/znc/Translation.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ZNC_TRANSLATION_H
|
||||
#define ZNC_TRANSLATION_H
|
||||
|
||||
#include <znc/ZNCString.h>
|
||||
#include <unordered_map>
|
||||
|
||||
// All instances of modules share single message map using this class stored in
|
||||
// CZNC.
|
||||
class CTranslation {
|
||||
public:
|
||||
static CTranslation& Get();
|
||||
CString Singular(const CString& sDomain, const CString& sContext,
|
||||
const CString& sEnglish);
|
||||
CString Plural(const CString& sDomain, const CString& sContext,
|
||||
const CString& sEnglish, const CString& sEnglishes,
|
||||
int iNum);
|
||||
|
||||
void PushLanguage(const CString& sLanguage);
|
||||
void PopLanguage();
|
||||
|
||||
void NewReference(const CString& sDomain);
|
||||
void DelReference(const CString& sDomain);
|
||||
|
||||
private:
|
||||
const std::locale& LoadTranslation(const CString& sDomain);
|
||||
std::unordered_map<CString /* domain */,
|
||||
std::unordered_map<CString /* language */, std::locale>>
|
||||
m_Translations;
|
||||
VCString m_sLanguageStack;
|
||||
std::unordered_map<CString /* domain */, int> m_miReferences;
|
||||
};
|
||||
|
||||
struct CLanguageScope {
|
||||
explicit CLanguageScope(const CString& sLanguage);
|
||||
~CLanguageScope();
|
||||
};
|
||||
|
||||
struct CTranslationDomainRefHolder {
|
||||
explicit CTranslationDomainRefHolder(const CString& sDomain);
|
||||
~CTranslationDomainRefHolder();
|
||||
|
||||
private:
|
||||
CString m_sDomain;
|
||||
};
|
||||
|
||||
// This is inspired by boost::locale::message, but without boost
|
||||
class CDelayedTranslation {
|
||||
public:
|
||||
CDelayedTranslation() = default;
|
||||
CDelayedTranslation(const CString& sDomain, const CString& sContext,
|
||||
const CString& sEnglish)
|
||||
: m_sDomain(sDomain), m_sContext(sContext), m_sEnglish(sEnglish) {}
|
||||
CString Resolve() const;
|
||||
|
||||
private:
|
||||
CString m_sDomain;
|
||||
CString m_sContext;
|
||||
CString m_sEnglish;
|
||||
};
|
||||
|
||||
// Used by everything except modules.
|
||||
// CModule defines its own version of these functions.
|
||||
class CCoreTranslationMixin {
|
||||
protected:
|
||||
static CString t(const CString& sEnglish, const CString& sContext = "");
|
||||
static CInlineFormatMessage f(const CString& sEnglish,
|
||||
const CString& sContext = "");
|
||||
static CInlineFormatMessage p(const CString& sEnglish,
|
||||
const CString& sEnglishes, int iNum,
|
||||
const CString& sContext = "");
|
||||
static CDelayedTranslation d(const CString& sEnglish,
|
||||
const CString& sContext = "");
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -143,6 +143,7 @@ class CUser {
|
||||
bool SetQueryBufferSize(unsigned int u, bool bForce = false);
|
||||
void SetAutoClearChanBuffer(bool b);
|
||||
void SetAutoClearQueryBuffer(bool b);
|
||||
bool SetLanguage(const CString& s);
|
||||
|
||||
void SetBeingDeleted(bool b) { m_bBeingDeleted = b; }
|
||||
void SetTimestampFormat(const CString& s) { m_sTimestampFormat = s; }
|
||||
@@ -200,6 +201,7 @@ class CUser {
|
||||
unsigned int JoinTries() const { return m_uMaxJoinTries; }
|
||||
unsigned int MaxJoins() const { return m_uMaxJoins; }
|
||||
CString GetSkinName() const;
|
||||
CString GetLanguage() const;
|
||||
unsigned int MaxNetworks() const { return m_uMaxNetworks; }
|
||||
unsigned int MaxQueryBuffers() const { return m_uMaxQueryBuffers; }
|
||||
// !Getters
|
||||
@@ -253,6 +255,7 @@ class CUser {
|
||||
unsigned int m_uMaxQueryBuffers;
|
||||
unsigned int m_uMaxJoins;
|
||||
CString m_sSkinName;
|
||||
CString m_sLanguage;
|
||||
|
||||
CModules* m_pModules;
|
||||
|
||||
|
||||
@@ -663,4 +663,38 @@ class MCString : public std::map<CString, CString> {
|
||||
virtual CString& Decode(CString& sValue) const;
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<CString> : hash<std::string> {};
|
||||
}
|
||||
|
||||
// Make translateable messages easy to write:
|
||||
// _f("Foo is {1}")(foo)
|
||||
class CInlineFormatMessage {
|
||||
public:
|
||||
explicit CInlineFormatMessage(const CString& sFormat)
|
||||
: m_sFormat(sFormat) {}
|
||||
explicit CInlineFormatMessage(CString&& sFormat)
|
||||
: m_sFormat(std::move(sFormat)) {}
|
||||
|
||||
template <typename... Args>
|
||||
CString operator()(const Args&... args) const {
|
||||
MCString values;
|
||||
apply(values, 1, args...);
|
||||
return CString::NamedFormat(m_sFormat, values);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Arg, typename... Rest>
|
||||
void apply(MCString& values, int index, const Arg& arg,
|
||||
const Rest&... rest) const {
|
||||
values[CString(index)] = CString(arg);
|
||||
apply(values, index + 1, rest...);
|
||||
}
|
||||
|
||||
void apply(MCString& values, int index) const {}
|
||||
|
||||
CString m_sFormat;
|
||||
};
|
||||
|
||||
#endif // !ZNCSTRING_H
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <znc/Modules.h>
|
||||
#include <znc/Socket.h>
|
||||
#include <znc/Listener.h>
|
||||
#include <znc/Translation.h>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <list>
|
||||
@@ -298,6 +299,7 @@ class CZNC {
|
||||
TCacheMap<CString> m_sConnectThrottle;
|
||||
bool m_bProtectWebSessions;
|
||||
bool m_bHideVersion;
|
||||
CTranslationDomainRefHolder m_Translation;
|
||||
};
|
||||
|
||||
#endif // !ZNC_H
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#cmakedefine HAVE_LIBSSL 1
|
||||
#cmakedefine HAVE_IPV6 1
|
||||
#cmakedefine HAVE_ZLIB 1
|
||||
#cmakedefine HAVE_I18N 1
|
||||
#cmakedefine CSOCK_USE_POLL 1
|
||||
|
||||
#cmakedefine HAVE_GETOPT_LONG 1
|
||||
@@ -49,5 +50,6 @@
|
||||
|
||||
#define _MODDIR_ "@CMAKE_INSTALL_FULL_LIBDIR@/znc"
|
||||
#define _DATADIR_ "@CMAKE_INSTALL_FULL_DATADIR@/znc"
|
||||
#define LOCALE_DIR "@CMAKE_INSTALL_FULL_LOCALEDIR@"
|
||||
|
||||
#endif /* ZNCCONFIG_H */
|
||||
|
||||
@@ -75,10 +75,19 @@ else()
|
||||
set(moddisable_modtcl true)
|
||||
endif()
|
||||
|
||||
set(actual_modules)
|
||||
|
||||
foreach(modpath ${all_modules})
|
||||
string(REGEX MATCH "/([-a-zA-Z0-9_]+)\\.([a-z]+)$" unused "${modpath}")
|
||||
if(NOT "${modpath}" MATCHES "/([-a-zA-Z0-9_]+)\\.([a-z]+)$")
|
||||
continue()
|
||||
endif()
|
||||
set(mod "${CMAKE_MATCH_1}")
|
||||
set(modtype "${CMAKE_MATCH_2}")
|
||||
if(mod STREQUAL "CMakeLists" OR mod STREQUAL "Makefile")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
list(APPEND actual_modules "${modpath}")
|
||||
|
||||
set(modenabled true)
|
||||
|
||||
@@ -107,5 +116,9 @@ foreach(modpath ${all_modules})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(HAVE_I18N)
|
||||
add_subdirectory(po)
|
||||
endif()
|
||||
|
||||
install(DIRECTORY data/
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/znc/modules")
|
||||
|
||||
24
modules/po/CMakeLists.txt
Normal file
24
modules/po/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
include(translation)
|
||||
|
||||
foreach(modpath ${actual_modules})
|
||||
get_filename_component(mod "${modpath}" NAME_WE)
|
||||
get_filename_component(module "${modpath}" NAME)
|
||||
translation(SHORT "${mod}" FULL "znc-${mod}" SOURCES "${module}"
|
||||
TMPLDIRS "${CMAKE_CURRENT_SOURCE_DIR}/../data/${mod}/tmpl")
|
||||
endforeach()
|
||||
@@ -19,13 +19,13 @@ if(CMAKE_VERSION VERSION_LESS 3.2)
|
||||
set_source_files_properties("versionc.cpp" PROPERTIES GENERATED true)
|
||||
endif()
|
||||
|
||||
znc_add_library(znclib ${lib_type} "ZNCString.cpp" "znc.cpp" "IRCNetwork.cpp"
|
||||
set(znc_cpp "ZNCString.cpp" "znc.cpp" "IRCNetwork.cpp" "Translation.cpp"
|
||||
"IRCSock.cpp" "Client.cpp" "Chan.cpp" "Nick.cpp" "Server.cpp"
|
||||
"Modules.cpp" "MD5.cpp" "Buffer.cpp" "Utils.cpp" "FileUtils.cpp"
|
||||
"HTTPSock.cpp" "Template.cpp" "ClientCommand.cpp" "Socket.cpp"
|
||||
"SHA256.cpp" "WebModules.cpp" "Listener.cpp" "Config.cpp" "ZNCDebug.cpp"
|
||||
"Threads.cpp" "Query.cpp" "SSLVerifyHost.cpp" "Message.cpp" "Csocket.cpp"
|
||||
"versionc.cpp" "User.cpp")
|
||||
"Threads.cpp" "Query.cpp" "SSLVerifyHost.cpp" "Message.cpp" "User.cpp")
|
||||
znc_add_library(znclib ${lib_type} ${znc_cpp} "Csocket.cpp" "versionc.cpp")
|
||||
znc_add_executable(znc "main.cpp")
|
||||
target_link_libraries(znc PRIVATE znclib)
|
||||
|
||||
@@ -76,6 +76,10 @@ if(ICU_FOUND)
|
||||
target_link_libraries(znclib ${ICU_LDFLAGS})
|
||||
list(APPEND znc_include_dirs ${ICU_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(Boost_FOUND)
|
||||
target_link_libraries(znclib ${Boost_LIBRARIES})
|
||||
list(APPEND znc_include_dirs ${Boost_INCLUDE_DIRS})
|
||||
endif()
|
||||
target_include_directories(znc PUBLIC ${znc_include_dirs})
|
||||
target_include_directories(znclib PUBLIC ${znc_include_dirs})
|
||||
|
||||
@@ -91,7 +95,9 @@ set_target_properties(znclib PROPERTIES
|
||||
OUTPUT_NAME "znc"
|
||||
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
|
||||
if(HAVE_I18N)
|
||||
add_subdirectory(po)
|
||||
endif()
|
||||
|
||||
|
||||
install(TARGETS znc ${install_lib}
|
||||
|
||||
@@ -94,6 +94,7 @@ void CClient::SendRequiredPasswordNotice() {
|
||||
}
|
||||
|
||||
void CClient::ReadLine(const CString& sData) {
|
||||
CLanguageScope user_lang(GetUser() ? GetUser()->GetLanguage() : "");
|
||||
CString sLine = sData;
|
||||
|
||||
sLine.TrimRight("\n\r");
|
||||
|
||||
@@ -138,6 +138,7 @@ CModule::CModule(ModHandle pDLL, CUser* pUser, CIRCNetwork* pNetwork,
|
||||
m_sSavePath(""),
|
||||
m_sArgs(""),
|
||||
m_sModPath(""),
|
||||
m_Translation("znc-" + sModName),
|
||||
m_mssRegistry(),
|
||||
m_vSubPages(),
|
||||
m_mCommands() {
|
||||
@@ -516,6 +517,13 @@ bool CModule::AddCommand(const CString& sCmd, const CString& sArgs,
|
||||
return AddCommand(std::move(cmd));
|
||||
}
|
||||
|
||||
bool CModule::AddCommand(const CString& sCmd, const CString& sArgs,
|
||||
const CDelayedTranslation& dDesc,
|
||||
std::function<void(const CString& sLine)> func) {
|
||||
CModCommand cmd(sCmd, std::move(func), sArgs, dDesc);
|
||||
return AddCommand(std::move(cmd));
|
||||
}
|
||||
|
||||
void CModule::AddHelpCommand() {
|
||||
AddCommand("Help", &CModule::HandleHelpCommand, "search",
|
||||
"Generate this output");
|
||||
@@ -1595,6 +1603,8 @@ bool CModules::LoadModule(const CString& sModule, const CString& sArgs,
|
||||
sRetMsg = "Unable to find module [" + sModule + "]";
|
||||
return false;
|
||||
}
|
||||
Info.SetName(sModule);
|
||||
Info.SetPath(sModPath);
|
||||
|
||||
ModHandle p =
|
||||
OpenModule(sModule, sModPath, bVersionMismatch, Info, sRetMsg);
|
||||
@@ -1754,14 +1764,13 @@ bool CModules::GetModPathInfo(CModInfo& ModInfo, const CString& sModule,
|
||||
const CString& sModPath, CString& sRetMsg) {
|
||||
bool bVersionMismatch;
|
||||
|
||||
ModHandle p =
|
||||
OpenModule(sModule, sModPath, bVersionMismatch, ModInfo, sRetMsg);
|
||||
|
||||
if (!p) return false;
|
||||
|
||||
ModInfo.SetName(sModule);
|
||||
ModInfo.SetPath(sModPath);
|
||||
|
||||
ModHandle p =
|
||||
OpenModule(sModule, sModPath, bVersionMismatch, ModInfo, sRetMsg);
|
||||
if (!p) return false;
|
||||
|
||||
if (bVersionMismatch) {
|
||||
ModInfo.SetDescription(
|
||||
"--- Version mismatch, recompile this module. ---");
|
||||
@@ -1910,6 +1919,7 @@ ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CTranslationDomainRefHolder translation("znc-" + sModule);
|
||||
typedef bool (*InfoFP)(double, CModInfo&);
|
||||
InfoFP ZNCModInfo = (InfoFP)dlsym(p, "ZNCModInfo");
|
||||
|
||||
@@ -1930,32 +1940,32 @@ ModHandle CModules::OpenModule(const CString& sModule, const CString& sModPath,
|
||||
return p;
|
||||
}
|
||||
|
||||
CModCommand::CModCommand() : m_sCmd(), m_pFunc(nullptr), m_sArgs(), m_sDesc() {}
|
||||
CModCommand::CModCommand()
|
||||
: m_sCmd(), m_pFunc(nullptr), m_sArgs(), m_sDesc(), m_bTranslating(false) {}
|
||||
|
||||
CModCommand::CModCommand(const CString& sCmd, CModule* pMod, ModCmdFunc func,
|
||||
const CString& sArgs, const CString& sDesc)
|
||||
: m_sCmd(sCmd),
|
||||
m_pFunc([pMod, func](const CString& sLine) { (pMod->*func)(sLine); }),
|
||||
m_sArgs(sArgs),
|
||||
m_sDesc(sDesc) {}
|
||||
m_sDesc(sDesc),
|
||||
m_bTranslating(false) {}
|
||||
|
||||
CModCommand::CModCommand(const CString& sCmd, CmdFunc func,
|
||||
const CString& sArgs, const CString& sDesc)
|
||||
: m_sCmd(sCmd), m_pFunc(std::move(func)), m_sArgs(sArgs), m_sDesc(sDesc) {}
|
||||
: m_sCmd(sCmd),
|
||||
m_pFunc(std::move(func)),
|
||||
m_sArgs(sArgs),
|
||||
m_sDesc(sDesc),
|
||||
m_bTranslating(false) {}
|
||||
|
||||
CModCommand::CModCommand(const CModCommand& other)
|
||||
: m_sCmd(other.m_sCmd),
|
||||
m_pFunc(other.m_pFunc),
|
||||
m_sArgs(other.m_sArgs),
|
||||
m_sDesc(other.m_sDesc) {}
|
||||
|
||||
CModCommand& CModCommand::operator=(const CModCommand& other) {
|
||||
m_sCmd = other.m_sCmd;
|
||||
m_pFunc = other.m_pFunc;
|
||||
m_sArgs = other.m_sArgs;
|
||||
m_sDesc = other.m_sDesc;
|
||||
return *this;
|
||||
}
|
||||
CModCommand::CModCommand(const CString& sCmd, CmdFunc func,
|
||||
const CString& sArgs, const CDelayedTranslation& dDesc)
|
||||
: m_sCmd(sCmd),
|
||||
m_pFunc(std::move(func)),
|
||||
m_sArgs(sArgs),
|
||||
m_dDesc(dDesc),
|
||||
m_bTranslating(true) {}
|
||||
|
||||
void CModCommand::InitHelp(CTable& Table) {
|
||||
Table.AddColumn("Command");
|
||||
@@ -1967,3 +1977,33 @@ void CModCommand::AddHelp(CTable& Table) const {
|
||||
Table.SetCell("Command", GetCommand() + " " + GetArgs());
|
||||
Table.SetCell("Description", GetDescription());
|
||||
}
|
||||
|
||||
CString CModCommand::GetDescription() const {
|
||||
return m_bTranslating ? m_dDesc.Resolve() : m_sDesc;
|
||||
}
|
||||
|
||||
CString CModule::t(const CString& sEnglish, const CString& sContext) const {
|
||||
return CTranslation::Get().Singular("znc-" + GetModName(), sContext,
|
||||
sEnglish);
|
||||
}
|
||||
|
||||
CInlineFormatMessage CModule::f(const CString& sEnglish,
|
||||
const CString& sContext) const {
|
||||
return CInlineFormatMessage(t(sEnglish, sContext));
|
||||
}
|
||||
|
||||
CInlineFormatMessage CModule::p(const CString& sEnglish,
|
||||
const CString& sEnglishes, int iNum,
|
||||
const CString& sContext) const {
|
||||
return CInlineFormatMessage(CTranslation::Get().Plural(
|
||||
"znc-" + GetModName(), sContext, sEnglish, sEnglishes, iNum));
|
||||
}
|
||||
|
||||
CDelayedTranslation CModule::d(const CString& sEnglish,
|
||||
const CString& sContext) const {
|
||||
return CDelayedTranslation("znc-" + GetModName(), sContext, sEnglish);
|
||||
}
|
||||
|
||||
CString CModInfo::t(const CString& sEnglish, const CString& sContext) const {
|
||||
return CTranslation::Get().Singular("znc-" + GetName(), sContext, sEnglish);
|
||||
}
|
||||
|
||||
@@ -607,3 +607,24 @@ void CIRCSocket::IcuExtFromUCallback(UConverterFromUnicodeArgs* fromArgs,
|
||||
err);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
CString CSocket::t(const CString& sEnglish, const CString& sContext) const {
|
||||
return GetModule()->t(sEnglish, sContext);
|
||||
}
|
||||
|
||||
CInlineFormatMessage CSocket::f(const CString& sEnglish,
|
||||
const CString& sContext) const {
|
||||
return GetModule()->f(sEnglish, sContext);
|
||||
}
|
||||
|
||||
CInlineFormatMessage CSocket::p(const CString& sEnglish,
|
||||
const CString& sEnglishes, int iNum,
|
||||
const CString& sContext) const {
|
||||
return GetModule()->p(sEnglish, sEnglishes, iNum, sContext);
|
||||
}
|
||||
|
||||
CDelayedTranslation CSocket::d(const CString& sEnglish,
|
||||
const CString& sContext) const {
|
||||
return GetModule()->d(sEnglish, sContext);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <znc/Template.h>
|
||||
#include <znc/FileUtils.h>
|
||||
#include <znc/ZNCDebug.h>
|
||||
#include <znc/Translation.h>
|
||||
#include <algorithm>
|
||||
|
||||
using std::stringstream;
|
||||
@@ -313,6 +314,10 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) {
|
||||
bool bLoopBreak = false;
|
||||
bool bExit = false;
|
||||
|
||||
// Single template works across multiple translation domains, e.g. .tmpl of
|
||||
// a module can INC'lude Footer.tmpl from core
|
||||
CString sI18N;
|
||||
|
||||
while (File.ReadLine(sLine)) {
|
||||
CString sOutput;
|
||||
bool bFoundATag = false;
|
||||
@@ -420,6 +425,12 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) {
|
||||
} else if (sAction.Equals("SETBLOCK")) {
|
||||
sSetBlockVar = sArgs;
|
||||
bInSetBlock = true;
|
||||
} else if (sAction.Equals("ENDSETBLOCK")) {
|
||||
CString sName = sSetBlockVar.Token(0);
|
||||
(*this)[sName] += sOutput;
|
||||
sOutput = "";
|
||||
bInSetBlock = false;
|
||||
sSetBlockVar = "";
|
||||
} else if (sAction.Equals("EXPAND")) {
|
||||
sOutput += ExpandFile(sArgs, true);
|
||||
} else if (sAction.Equals("VAR")) {
|
||||
@@ -536,6 +547,50 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) {
|
||||
}
|
||||
} else if (sAction.Equals("REM")) {
|
||||
uSkip++;
|
||||
} else if (sAction.Equals("I18N")) {
|
||||
sI18N = sArgs;
|
||||
} else if (sAction.Equals("FORMAT") ||
|
||||
sAction.Equals("PLURAL")) {
|
||||
bool bHaveContext = false;
|
||||
if (sArgs.TrimPrefix("CTX=")) {
|
||||
bHaveContext = true;
|
||||
}
|
||||
VCString vsArgs;
|
||||
sArgs.QuoteSplit(vsArgs);
|
||||
CString sEnglish, sEnglishes, sContext;
|
||||
int idx = 0;
|
||||
if (bHaveContext && vsArgs.size() > idx) {
|
||||
sContext = vsArgs[idx];
|
||||
idx++;
|
||||
}
|
||||
if (vsArgs.size() > idx) {
|
||||
sEnglish = vsArgs[idx];
|
||||
idx++;
|
||||
}
|
||||
CString sFormat;
|
||||
if (sAction.Equals("PLURAL")) {
|
||||
CString sEnglishes;
|
||||
int iNum = 0;
|
||||
if (vsArgs.size() > idx) {
|
||||
sEnglishes = vsArgs[idx];
|
||||
idx++;
|
||||
}
|
||||
if (vsArgs.size() > idx) {
|
||||
iNum = GetValue(vsArgs[idx], true).ToInt();
|
||||
idx++;
|
||||
}
|
||||
sFormat = CTranslation::Get().Plural(
|
||||
sI18N, sContext, sEnglish, sEnglishes, iNum);
|
||||
} else {
|
||||
sFormat = CTranslation::Get().Singular(
|
||||
sI18N, sContext, sEnglish);
|
||||
}
|
||||
MCString msParams;
|
||||
for (int i = 0; i + idx < vsArgs.size(); ++i) {
|
||||
msParams[CString(i + 1)] =
|
||||
GetValue(vsArgs[i + idx], false);
|
||||
}
|
||||
sOutput += CString::NamedFormat(sFormat, msParams);
|
||||
} else {
|
||||
bNotFound = true;
|
||||
}
|
||||
@@ -557,9 +612,6 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) {
|
||||
if (uSkip) {
|
||||
uSkip--;
|
||||
}
|
||||
} else if (sAction.Equals("ENDSETBLOCK")) {
|
||||
bInSetBlock = false;
|
||||
sSetBlockVar = "";
|
||||
} else if (sAction.Equals("ENDLOOP")) {
|
||||
if (bLoopCont && uSkip == 1) {
|
||||
uSkip--;
|
||||
|
||||
@@ -29,8 +29,6 @@ static const size_t MAX_IDLE_THREADS = 3;
|
||||
static const size_t MAX_TOTAL_THREADS = 20;
|
||||
|
||||
CThreadPool& CThreadPool::Get() {
|
||||
// Beware! The following is not thread-safe! This function must
|
||||
// be called once any thread is started.
|
||||
static CThreadPool pool;
|
||||
return pool;
|
||||
}
|
||||
|
||||
128
src/Translation.cpp
Normal file
128
src/Translation.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <znc/Translation.h>
|
||||
|
||||
#ifdef HAVE_I18N
|
||||
#include <boost/locale.hpp>
|
||||
#endif
|
||||
|
||||
CTranslation& CTranslation::Get() {
|
||||
static CTranslation translation;
|
||||
return translation;
|
||||
}
|
||||
|
||||
CString CTranslation::Singular(const CString& sDomain, const CString& sContext,
|
||||
const CString& sEnglish) {
|
||||
#ifdef HAVE_I18N
|
||||
const std::locale& loc = LoadTranslation(sDomain);
|
||||
return boost::locale::translate(sContext, sEnglish).str(loc);
|
||||
#else
|
||||
return sEnglish;
|
||||
#endif
|
||||
}
|
||||
CString CTranslation::Plural(const CString& sDomain, const CString& sContext,
|
||||
const CString& sEnglish, const CString& sEnglishes,
|
||||
int iNum) {
|
||||
#ifdef HAVE_I18N
|
||||
const std::locale& loc = LoadTranslation(sDomain);
|
||||
return boost::locale::translate(sContext, sEnglish, sEnglishes, iNum)
|
||||
.str(loc);
|
||||
#else
|
||||
if (iNum == 1) {
|
||||
return sEnglish;
|
||||
} else {
|
||||
return sEnglishes;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::locale& CTranslation::LoadTranslation(const CString& sDomain) {
|
||||
CString sLanguage = m_sLanguageStack.empty() ? "" : m_sLanguageStack.back();
|
||||
#ifdef HAVE_I18N
|
||||
// Not using built-in support for multiple domains in single std::locale
|
||||
// via overloaded call to .str() because we need to be able to reload
|
||||
// translations from disk independently when a module gets updated
|
||||
auto& domain = m_Translations[sDomain];
|
||||
auto lang_it = domain.find(sLanguage);
|
||||
if (lang_it == domain.end()) {
|
||||
boost::locale::generator gen;
|
||||
gen.add_messages_path(LOCALE_DIR);
|
||||
gen.add_messages_domain(sDomain);
|
||||
std::tie(lang_it, std::ignore) =
|
||||
domain.emplace(sLanguage, gen(sLanguage + ".UTF-8"));
|
||||
}
|
||||
return lang_it->second;
|
||||
#else
|
||||
// dummy, it's not used anyway
|
||||
return std::locale::classic();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CTranslation::PushLanguage(const CString& sLanguage) {
|
||||
m_sLanguageStack.push_back(sLanguage);
|
||||
}
|
||||
void CTranslation::PopLanguage() { m_sLanguageStack.pop_back(); }
|
||||
|
||||
void CTranslation::NewReference(const CString& sDomain) {
|
||||
m_miReferences[sDomain]++;
|
||||
}
|
||||
void CTranslation::DelReference(const CString& sDomain) {
|
||||
if (!--m_miReferences[sDomain]) {
|
||||
m_Translations.erase(sDomain);
|
||||
}
|
||||
}
|
||||
|
||||
CString CCoreTranslationMixin::t(const CString& sEnglish,
|
||||
const CString& sContext) {
|
||||
return CTranslation::Get().Singular("znc", sContext, sEnglish);
|
||||
}
|
||||
|
||||
CInlineFormatMessage CCoreTranslationMixin::f(const CString& sEnglish,
|
||||
const CString& sContext) {
|
||||
return CInlineFormatMessage(t(sEnglish, sContext));
|
||||
}
|
||||
|
||||
CInlineFormatMessage CCoreTranslationMixin::p(const CString& sEnglish,
|
||||
const CString& sEnglishes,
|
||||
int iNum,
|
||||
const CString& sContext) {
|
||||
return CInlineFormatMessage(CTranslation::Get().Plural(
|
||||
"znc", sContext, sEnglish, sEnglishes, iNum));
|
||||
}
|
||||
|
||||
CDelayedTranslation CCoreTranslationMixin::d(const CString& sEnglish,
|
||||
const CString& sContext) {
|
||||
return CDelayedTranslation("znc", sContext, sEnglish);
|
||||
}
|
||||
|
||||
|
||||
CLanguageScope::CLanguageScope(const CString& sLanguage) {
|
||||
CTranslation::Get().PushLanguage(sLanguage);
|
||||
}
|
||||
CLanguageScope::~CLanguageScope() { CTranslation::Get().PopLanguage(); }
|
||||
|
||||
CString CDelayedTranslation::Resolve() const {
|
||||
return CTranslation::Get().Singular(m_sDomain, m_sContext, m_sEnglish);
|
||||
}
|
||||
|
||||
CTranslationDomainRefHolder::CTranslationDomainRefHolder(const CString& sDomain)
|
||||
: m_sDomain(sDomain) {
|
||||
CTranslation::Get().NewReference(sDomain);
|
||||
}
|
||||
CTranslationDomainRefHolder::~CTranslationDomainRefHolder() {
|
||||
CTranslation::Get().DelReference(m_sDomain);
|
||||
}
|
||||
18
src/User.cpp
18
src/User.cpp
@@ -272,6 +272,9 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pConfig->FindStringEntry("language", sValue)) {
|
||||
SetLanguage(sValue);
|
||||
}
|
||||
pConfig->FindStringEntry("pass", sValue);
|
||||
// There are different formats for this available:
|
||||
// Pass = <plain text>
|
||||
@@ -749,6 +752,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
|
||||
SetMaxQueryBuffers(User.MaxQueryBuffers());
|
||||
SetMaxJoins(User.MaxJoins());
|
||||
SetClientEncoding(User.GetClientEncoding());
|
||||
SetLanguage(User.GetLanguage());
|
||||
|
||||
// Allowed Hosts
|
||||
m_ssAllowedHosts.clear();
|
||||
@@ -1057,6 +1061,7 @@ CConfig CUser::ToConfig() const {
|
||||
config.AddKeyValuePair("MaxQueryBuffers", CString(m_uMaxQueryBuffers));
|
||||
config.AddKeyValuePair("MaxJoins", CString(m_uMaxJoins));
|
||||
config.AddKeyValuePair("ClientEncoding", GetClientEncoding());
|
||||
config.AddKeyValuePair("Language", GetLanguage());
|
||||
|
||||
// Allow Hosts
|
||||
if (!m_ssAllowedHosts.empty()) {
|
||||
@@ -1398,6 +1403,18 @@ bool CUser::SetStatusPrefix(const CString& s) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CUser::SetLanguage(const CString& s) {
|
||||
// They look like ru_RU
|
||||
for (char c : s) {
|
||||
if (isalpha(c) || c == '_') {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_sLanguage = s;
|
||||
return true;
|
||||
}
|
||||
// !Setters
|
||||
|
||||
// Getters
|
||||
@@ -1462,6 +1479,7 @@ bool CUser::AutoClearQueryBuffer() const { return m_bAutoClearQueryBuffer; }
|
||||
// CString CUser::GetSkinName() const { return (!m_sSkinName.empty()) ?
|
||||
// m_sSkinName : CZNC::Get().GetSkinName(); }
|
||||
CString CUser::GetSkinName() const { return m_sSkinName; }
|
||||
CString CUser::GetLanguage() const { return m_sLanguage; }
|
||||
const CString& CUser::GetUserPath() const {
|
||||
if (!CFile::Exists(m_sUserPath)) {
|
||||
CDir::MakeDir(m_sUserPath);
|
||||
|
||||
@@ -672,6 +672,8 @@ CWebSock::EPageReqResult CWebSock::OnPageRequestInternal(const CString& sURI,
|
||||
m_sUser = GetSession()->GetUser()->GetUserName();
|
||||
m_bLoggedIn = true;
|
||||
}
|
||||
CLanguageScope user_language(
|
||||
m_bLoggedIn ? GetSession()->GetUser()->GetLanguage() : "");
|
||||
|
||||
// Handle the static pages that don't require a login
|
||||
if (sURI == "/") {
|
||||
|
||||
25
src/po/CMakeLists.txt
Normal file
25
src/po/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
include(translation)
|
||||
|
||||
set(tmpl_dirs)
|
||||
file(GLOB skins "${PROJECT_SOURCE_DIR}/webskins/*")
|
||||
foreach(skin ${skins})
|
||||
list(APPEND tmpl_dirs "${skin}/tmpl")
|
||||
endforeach()
|
||||
translation(SHORT "znc" FULL "znc" SOURCES ${znc_cpp}
|
||||
TMPLDIRS ${tmpl_dirs})
|
||||
16
src/znc.cpp
16
src/znc.cpp
@@ -75,7 +75,8 @@ CZNC::CZNC()
|
||||
m_uiForceEncoding(0),
|
||||
m_sConnectThrottle(),
|
||||
m_bProtectWebSessions(true),
|
||||
m_bHideVersion(false) {
|
||||
m_bHideVersion(false),
|
||||
m_Translation("znc") {
|
||||
if (!InitCsocket()) {
|
||||
CUtils::PrintError("Could not initialize Csocket!");
|
||||
exit(-1);
|
||||
@@ -160,11 +161,17 @@ CString CZNC::GetCompileOptionsString() {
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
", build: "
|
||||
", build: "
|
||||
#ifdef BUILD_WITH_CMAKE
|
||||
"cmake"
|
||||
"cmake"
|
||||
#else
|
||||
"autoconf"
|
||||
"autoconf"
|
||||
#endif
|
||||
", i18n: "
|
||||
#ifdef HAVE_I18N
|
||||
"yes"
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
;
|
||||
}
|
||||
@@ -1419,6 +1426,7 @@ void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly, CUser* pSkipUser,
|
||||
if (bAdminOnly && !it.second->IsAdmin()) continue;
|
||||
|
||||
if (it.second != pSkipUser) {
|
||||
// TODO: translate message to user's language
|
||||
CString sMsg = sMessage;
|
||||
|
||||
bool bContinue = false;
|
||||
|
||||
81
translation_pot.py
Executable file
81
translation_pot.py
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (C) 2004-2016 ZNC, see the NOTICE file for details.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--include_dir', action='store')
|
||||
parser.add_argument('--explicit_sources', action='append')
|
||||
parser.add_argument('--tmpl_dirs', action='append')
|
||||
parser.add_argument('--strip_prefix', action='store')
|
||||
parser.add_argument('--tmp_prefix', action='store')
|
||||
parser.add_argument('--output', action='store')
|
||||
args = parser.parse_args()
|
||||
|
||||
pot_list = []
|
||||
|
||||
# .cpp
|
||||
main_pot = args.tmp_prefix + '_main.pot'
|
||||
subprocess.check_call(['xgettext',
|
||||
'--omit-header',
|
||||
'-D', args.include_dir,
|
||||
'-o', main_pot,
|
||||
'--keyword=t:1,1t', '--keyword=t:1,2c,2t',
|
||||
'--keyword=f:1,1t', '--keyword=f:1,2c,2t',
|
||||
'--keyword=p:1,2,3t', '--keyword=p:1,2,4c,4t',
|
||||
'--keyword=d:1,1t', '--keyword=d:1,2c,2t',
|
||||
] + args.explicit_sources)
|
||||
if os.path.isfile(main_pot):
|
||||
pot_list.append(main_pot)
|
||||
|
||||
# .tmpl
|
||||
tmpl_pot = args.tmp_prefix + '_tmpl.pot'
|
||||
tmpl_uniq_pot = args.tmp_prefix + '_tmpl_uniq.pot'
|
||||
tmpl = []
|
||||
pattern = re.compile(r'<\?\s*(?:FORMAT|(PLURAL))\s+(?:CTX="([^"]+?)"\s+)?"([^"]+?)"(?(1)\s+"([^"]+?)"|).*?\?>')
|
||||
for tmpl_dir in args.tmpl_dirs:
|
||||
for fname in glob.iglob(tmpl_dir + '/*.tmpl'):
|
||||
fbase = fname[len(args.strip_prefix):]
|
||||
with open(fname) as f:
|
||||
for linenum, line in enumerate(f):
|
||||
for x in pattern.finditer(line):
|
||||
text, plural, context = x.group(3), x.group(4), x.group(2)
|
||||
tmpl.append('#: {}:{}'.format(fbase, linenum + 1))
|
||||
if context:
|
||||
tmpl.append('msgctxt "{}"'.format(context))
|
||||
tmpl.append('msgid "{}"'.format(text))
|
||||
if plural:
|
||||
tmpl.append('msgid_plural "{}"'.format(plural))
|
||||
tmpl.append('msgstr[0] ""')
|
||||
tmpl.append('msgstr[1] ""')
|
||||
else:
|
||||
tmpl.append('msgstr ""')
|
||||
tmpl.append('')
|
||||
if tmpl:
|
||||
with open(tmpl_pot, 'w') as f:
|
||||
for line in tmpl:
|
||||
print(line, file=f)
|
||||
subprocess.check_call(['msguniq', '-o', tmpl_uniq_pot, tmpl_pot])
|
||||
pot_list.append(tmpl_uniq_pot)
|
||||
|
||||
# combine
|
||||
if pot_list:
|
||||
subprocess.check_call(['msgcat', '-o', args.output] + pot_list)
|
||||
Reference in New Issue
Block a user