Merge pull request #684 from wesnoth/spirit_po
Support loading translations from PO files closes #684
This commit is contained in:
commit
c3ee417855
25 changed files with 1788 additions and 541 deletions
|
@ -53,7 +53,6 @@ option(ENABLE_TESTS "Build unit tests")
|
|||
option(ENABLE_NLS "Enable building of translations" ON)
|
||||
option(ENABLE_OMP "Enables OpenMP, and has additional dependencies" OFF)
|
||||
option(ENABLE_LIBPNG "Enable support for writing png files (screenshots, images)" ON)
|
||||
option(ENABLE_LIBINTL "Enable using libintl for translations instead of Boost.Locale library (not recommended)" OFF)
|
||||
option(ENABLE_HISTORY "Enable using GNU history for history in lua console" ON)
|
||||
|
||||
if(DEFINED ENV{TRAVIS})
|
||||
|
@ -609,15 +608,7 @@ endif(ENABLE_GAME)
|
|||
if(ENABLE_GAME OR ENABLE_SERVER OR ENABLE_TOOLS OR ENABLE_TESTS)
|
||||
find_package( Boost 1.44 REQUIRED COMPONENTS filesystem )
|
||||
|
||||
if(NOT ENABLE_LIBINTL)
|
||||
find_package( Boost 1.48 REQUIRED COMPONENTS locale )
|
||||
else()
|
||||
find_package( Libintl REQUIRED )
|
||||
if(WIN32)
|
||||
# CMake 2.6 doesn't do message(WARNING ...)
|
||||
message("Warning: Using libintl on Windows instead of Boost.Locale may result in issues with translations and Unicode file paths!")
|
||||
endif()
|
||||
endif()
|
||||
find_package( Boost 1.48 REQUIRED COMPONENTS locale )
|
||||
endif(ENABLE_GAME OR ENABLE_SERVER OR ENABLE_TOOLS OR ENABLE_TESTS)
|
||||
|
||||
if(ENABLE_POT_UPDATE_TARGET)
|
||||
|
|
14
SConstruct
14
SConstruct
|
@ -72,7 +72,6 @@ opts.AddVariables(
|
|||
PathVariable('python_site_packages_dir', 'sets the directory where python modules are installed', "lib/python/site-packages/wesnoth", PathVariable.PathAccept),
|
||||
BoolVariable('notifications', 'Enable support for desktop notifications', True),
|
||||
BoolVariable('nls','enable compile/install of gettext message catalogs',True),
|
||||
BoolVariable('libintl', 'Use lib intl for translations, instead of boost locale', False),
|
||||
BoolVariable('png', 'Clear to disable writing png files for screenshots, images', True),
|
||||
PathVariable('prefix', 'autotools-style installation prefix', "/usr/local", PathVariable.PathAccept),
|
||||
PathVariable('prefsdir', 'user preferences directory', "", PathVariable.PathAccept),
|
||||
|
@ -349,13 +348,6 @@ if env["prereqs"]:
|
|||
conf.CheckSDL("SDL2_mixer", header_file = "SDL_mixer") & \
|
||||
conf.CheckSDL("SDL2_image", header_file = "SDL_image")
|
||||
|
||||
if env["libintl"]:
|
||||
def have_i18n_prereqs():
|
||||
return conf.CheckGettextLibintl()
|
||||
else:
|
||||
def have_i18n_prereqs():
|
||||
return conf.CheckBoost("locale")
|
||||
|
||||
have_server_prereqs = (\
|
||||
conf.CheckCPlusPlus(gcc_version = "4.8") & \
|
||||
conf.CheckBoost("iostreams", require_version = boost_version) & \
|
||||
|
@ -366,7 +358,7 @@ if env["prereqs"]:
|
|||
conf.CheckBoost("smart_ptr", header_only = True) & \
|
||||
conf.CheckBoost("system") & \
|
||||
conf.CheckBoost("filesystem", require_version = boost_version) & \
|
||||
have_i18n_prereqs() \
|
||||
conf.CheckBoost("locale") \
|
||||
and Info("Base prerequisites are met")) \
|
||||
or Warning("Base prerequisites are not met")
|
||||
|
||||
|
@ -390,10 +382,6 @@ if env["prereqs"]:
|
|||
if have_client_prereqs:
|
||||
if env["PLATFORM"] != "win32":
|
||||
have_X = conf.CheckLib('X11')
|
||||
else:
|
||||
if env["libintl"]:
|
||||
Warning("You cannot use the libintl option when building for windows")
|
||||
have_client_prereqs = False
|
||||
|
||||
env["notifications"] = env["notifications"] and conf.CheckPKG("dbus-1")
|
||||
if env["notifications"]:
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# Try to find Libintl functionality
|
||||
# Once done this will define
|
||||
#
|
||||
# LIBINTL_FOUND - system has Libintl
|
||||
# LIBINTL_INCLUDE_DIR - Libintl include directory
|
||||
# LIBINTL_LIBRARIES - Libraries needed to use Libintl
|
||||
#
|
||||
# TODO: This will enable translations only if Gettext functionality is
|
||||
# present in libc. Must have more robust system for release, where Gettext
|
||||
# functionality can also reside in standalone Gettext library, or the one
|
||||
# embedded within kdelibs (cf. gettext.m4 from Gettext source).
|
||||
|
||||
# Copyright (c) 2006, Chusslove Illich, <caslav.ilic@gmx.net>
|
||||
# Copyright (c) 2007, Alexander Neundorf, <neundorf@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
if(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND)
|
||||
set(Libintl_FIND_QUIETLY TRUE)
|
||||
endif(LIBINTL_INCLUDE_DIR AND LIBINTL_LIB_FOUND)
|
||||
|
||||
find_path(LIBINTL_INCLUDE_DIR libintl.h)
|
||||
|
||||
set(LIBINTL_LIB_FOUND FALSE)
|
||||
|
||||
if(LIBINTL_INCLUDE_DIR)
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(dgettext LIBINTL_LIBC_HAS_DGETTEXT)
|
||||
|
||||
if (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||
set(LIBINTL_LIBRARIES)
|
||||
set(LIBINTL_LIB_FOUND TRUE)
|
||||
else (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||
find_library(LIBINTL_LIBRARIES NAMES intl libintl )
|
||||
if(LIBINTL_LIBRARIES)
|
||||
set(LIBINTL_LIB_FOUND TRUE)
|
||||
endif(LIBINTL_LIBRARIES)
|
||||
endif (LIBINTL_LIBC_HAS_DGETTEXT)
|
||||
|
||||
endif(LIBINTL_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Libintl DEFAULT_MSG LIBINTL_INCLUDE_DIR LIBINTL_LIB_FOUND)
|
||||
|
||||
mark_as_advanced(LIBINTL_INCLUDE_DIR LIBINTL_LIBRARIES LIBINTL_LIBC_HAS_DGETTEXT LIBINTL_LIB_FOUND)
|
|
@ -1,178 +0,0 @@
|
|||
# - Find Gettext run-time library and tools.
|
||||
# This module finds the GNU gettext run-time library (LGPL), include paths and
|
||||
# associated tools (GPL). This code sets the following variables:
|
||||
# GETTEXT_INCLUDE_DIR = path(s) to gettext's include files
|
||||
# GETTEXT_LIBRARIES = the libraries to link against to use gettext
|
||||
# GETTEXT_INTL_LIBRARY = path to gettext's intl library
|
||||
# GETTEXT_RUNTIME_FOUND = true if runtime libs were found (intl)
|
||||
# GETTEXT_INFO_MSG = information string about gettext
|
||||
# GETTEXT_XGETTEXT_EXECUTABLE = xgettext tool
|
||||
# GETTEXT_MSGINIT_EXECUTABLE = msginit tool
|
||||
# GETTEXT_MSGCAT_EXECUTABLE = msgcat tool
|
||||
# GETTEXT_MSGCONV_EXECUTABLE = msgconv tool
|
||||
# GETTEXT_TOOLS_FOUND = true if all the tools were found
|
||||
# GETTEXT_FOUND = true if both runtime and tools were found
|
||||
# As a convenience, the following variables can be set before including
|
||||
# this module to make its life easier:
|
||||
# GETTEXT_SEARCH_PATH = list of path to search gettext components for
|
||||
# --------------------------------------------------------------------------
|
||||
# As a convenience, try to find everything as soon as we set any one of
|
||||
# the cache variables.
|
||||
|
||||
macro(GETTEXT_FIND_POTENTIAL_DIRS)
|
||||
|
||||
set(potential_bin_dirs)
|
||||
set(potential_lib_dirs)
|
||||
set(potential_include_dirs)
|
||||
foreach(filepath
|
||||
"${GETTEXT_INTL_LIBRARY}"
|
||||
"${GETTEXT_XGETTEXT_EXECUTABLE}"
|
||||
"${GETTEXT_MSGINIT_EXECUTABLE}"
|
||||
"${GETTEXT_MSGCAT_EXECUTABLE}"
|
||||
"${GETTEXT_MSGCONV_EXECUTABLE}"
|
||||
)
|
||||
get_filename_component(path "${filepath}" PATH)
|
||||
set(potential_bin_dirs ${potential_bin_dirs} "${path}/../bin")
|
||||
set(potential_lib_dirs ${potential_lib_dirs} "${path}/../lib")
|
||||
set(potential_include_dirs ${potential_include_dirs} "${path}/../include")
|
||||
endforeach(filepath)
|
||||
|
||||
foreach(path
|
||||
"${GETTEXT_INCLUDE_DIR}"
|
||||
"${GETTEXT_SEARCH_PATH}"
|
||||
)
|
||||
set(potential_bin_dirs ${potential_bin_dirs} "${path}/../bin")
|
||||
set(potential_lib_dirs ${potential_lib_dirs} "${path}/../lib")
|
||||
set(potential_include_dirs ${potential_include_dirs} "${path}/../include")
|
||||
endforeach(path)
|
||||
|
||||
endmacro(GETTEXT_FIND_POTENTIAL_DIRS)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Find the runtime lib
|
||||
|
||||
macro(GETTEXT_FIND_RUNTIME_LIBRARY)
|
||||
|
||||
set(GETTEXT_RUNTIME_FOUND 1)
|
||||
|
||||
# The gettext intl include dir (libintl.h)
|
||||
|
||||
find_path(GETTEXT_INCLUDE_DIR
|
||||
libintl.h
|
||||
${potential_include_dirs}
|
||||
DOC "Path to gettext include directory (where libintl.h can be found)")
|
||||
mark_as_advanced(GETTEXT_INCLUDE_DIR)
|
||||
if(NOT GETTEXT_INCLUDE_DIR)
|
||||
set(GETTEXT_RUNTIME_FOUND 0)
|
||||
endif(NOT GETTEXT_INCLUDE_DIR)
|
||||
|
||||
set(GETTEXT_LIBRARIES)
|
||||
|
||||
# The gettext intl library
|
||||
# Some Unix system (like Linux) have gettext right into libc
|
||||
|
||||
if(WIN32)
|
||||
set(HAVE_GETTEXT 0)
|
||||
else(WIN32)
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(gettext HAVE_GETTEXT)
|
||||
endif(WIN32)
|
||||
|
||||
if(HAVE_GETTEXT)
|
||||
# Even if we have a system one, let the user provide another one
|
||||
# eventually (i.e., more recent, or GNU).
|
||||
set(GETTEXT_INTL_LIBRARY "" CACHE FILEPATH
|
||||
"Path to gettext intl library (leave it empty to use the system one)")
|
||||
else(HAVE_GETTEXT)
|
||||
find_library(GETTEXT_INTL_LIBRARY
|
||||
NAMES intl
|
||||
PATHS ${potential_lib_dirs}
|
||||
DOC "Path to gettext intl library")
|
||||
if(NOT GETTEXT_INTL_LIBRARY)
|
||||
set(GETTEXT_RUNTIME_FOUND 0)
|
||||
endif(NOT GETTEXT_INTL_LIBRARY)
|
||||
endif(HAVE_GETTEXT)
|
||||
|
||||
mark_as_advanced(GETTEXT_INTL_LIBRARY)
|
||||
if(GETTEXT_INTL_LIBRARY)
|
||||
set(GETTEXT_LIBRARIES ${GETTEXT_LIBRARIES} ${GETTEXT_INTL_LIBRARY})
|
||||
endif(GETTEXT_INTL_LIBRARY)
|
||||
|
||||
# The gettext asprintf library
|
||||
# Actually not useful as it does not seem to exist on Unix
|
||||
|
||||
# IF(WIN32)
|
||||
# FIND_LIBRARY(GETTEXT_ASPRINTF_LIBRARY
|
||||
# NAMES asprintf
|
||||
# PATHS ${potential_lib_dirs}
|
||||
# DOC "Gettext asprintf library")
|
||||
# MARK_AS_ADVANCED(GETTEXT_ASPRINTF_LIBRARY)
|
||||
# IF(NOT GETTEXT_ASPRINTF_LIBRARY)
|
||||
# SET(GETTEXT_RUNTIME_FOUND 0)
|
||||
# ELSE(NOT GETTEXT_ASPRINTF_LIBRARY)
|
||||
# SET(GETTEXT_LIBRARIES ${GETTEXT_LIBRARIES} ${GETTEXT_ASPRINTF_LIBRARY})
|
||||
# ENDIF(NOT GETTEXT_ASPRINTF_LIBRARY)
|
||||
# ENDIF(WIN32)
|
||||
|
||||
endmacro(GETTEXT_FIND_RUNTIME_LIBRARY)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Find the tools
|
||||
|
||||
macro(GETTEXT_FIND_TOOLS)
|
||||
set(GETTEXT_TOOLS_FOUND 1)
|
||||
foreach(tool
|
||||
xgettext
|
||||
msginit
|
||||
msgmerge
|
||||
msgcat
|
||||
msgconv
|
||||
msgfmt
|
||||
)
|
||||
string(TOUPPER ${tool} tool_upper)
|
||||
find_program(GETTEXT_${tool_upper}_EXECUTABLE
|
||||
NAMES ${tool}
|
||||
PATHS ${potential_bin_dirs}
|
||||
DOC "Path to gettext ${tool} tool")
|
||||
mark_as_advanced(GETTEXT_${tool_upper}_EXECUTABLE)
|
||||
if(NOT GETTEXT_${tool_upper}_EXECUTABLE)
|
||||
set(GETTEXT_TOOLS_FOUND 0)
|
||||
endif(NOT GETTEXT_${tool_upper}_EXECUTABLE)
|
||||
endforeach(tool)
|
||||
endmacro(GETTEXT_FIND_TOOLS)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Some convenient info about gettext, where to get it, etc.
|
||||
|
||||
set(GETTEXT_INFO_MSG "More information about gettext can be found at http://directory.fsf.org/gettext.html.")
|
||||
if(WIN32)
|
||||
set(GETTEXT_INFO_MSG "${GETTEXT_INFO_MSG} Windows users can download gettext-runtime-0.13.1.bin.woe32.zip (LGPL), gettext-tools-0.13.1.bin.woe32.zip (GPL) as well as libiconv-1.9.1.bin.woe32.zip (LGPL) from any GNU mirror (say, http://mirrors.kernel.org/gnu/gettext/ and http://mirrors.kernel.org/gnu/libiconv/), unpack the archives in the same directory, then set GETTEXT_INTL_LIBRARY to 'lib/intl.lib' in and GETTEXT_INCLUDE_DIR to 'include' in that directory.\n\nWarning: if you are using ActiveTcl, the ActiveState binary distribution for Tcl, make sure you overwrite the iconv.dll file found in both the Tcl bin/ and lib/ directories with the iconv.dll file found in your gettext bin/ directory.")
|
||||
endif(WIN32)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Found ?
|
||||
|
||||
gettext_find_potential_dirs()
|
||||
gettext_find_runtime_library()
|
||||
gettext_find_tools()
|
||||
|
||||
# Try again with new potential dirs now that we may have found the runtime
|
||||
# or the tools
|
||||
|
||||
gettext_find_potential_dirs()
|
||||
if(NOT GETTEXT_RUNTIME_FOUND)
|
||||
gettext_find_runtime_library()
|
||||
endif(NOT GETTEXT_RUNTIME_FOUND)
|
||||
if(NOT GETTEXT_TOOLS_FOUND)
|
||||
gettext_find_tools()
|
||||
endif(NOT GETTEXT_TOOLS_FOUND)
|
||||
|
||||
if(GETTEXT_RUNTIME_FOUND AND GETTEXT_TOOLS_FOUND)
|
||||
set(GETTEXT_FOUND 1)
|
||||
else(GETTEXT_RUNTIME_FOUND AND GETTEXT_TOOLS_FOUND)
|
||||
set(GETTEXT_FOUND 0)
|
||||
endif(GETTEXT_RUNTIME_FOUND AND GETTEXT_TOOLS_FOUND)
|
||||
|
||||
if(NOT GETTEXT_FOUND AND NOT Gettext_FIND_QUIETLY AND Gettext_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could not find gettext runtime library and tools for internationalization purposes.\n\n${GETTEXT_INFO_MSG}")
|
||||
endif(NOT GETTEXT_FOUND AND NOT Gettext_FIND_QUIETLY AND Gettext_FIND_REQUIRED)
|
|
@ -1,9 +1,5 @@
|
|||
Files in this folder will be used to help cmake. The origin of the scripts, if they are not written by us will be noted here, too, so that it is easy to update them.
|
||||
|
||||
file: FindLibintl.cmake
|
||||
url: http://websvn.kde.org/*checkout*/trunk/KDE/kdelibs/cmake/modules/FindLibintl.cmake
|
||||
revision: 797757
|
||||
|
||||
file: FindGettext.cmake
|
||||
url: http://kwwidgets.org/cgi-bin/viewcvs.cgi/CMake/FindGettext.cmake?root=KWWidgets&view=markup
|
||||
revision: 1.2
|
||||
|
|
|
@ -466,7 +466,6 @@
|
|||
91B622131B76C0A600B00E0F /* libgthread-2.0.0.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242A18EF07B4001FA499 /* libgthread-2.0.0.dylib */; };
|
||||
91B622141B76C0A600B00E0F /* libharfbuzz.0.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242B18EF07B4001FA499 /* libharfbuzz.0.dylib */; };
|
||||
91B622151B76C0A600B00E0F /* libiconv.2.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242C18EF07B4001FA499 /* libiconv.2.dylib */; };
|
||||
91B622161B76C0A600B00E0F /* libintl.8.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242D18EF07B4001FA499 /* libintl.8.dylib */; };
|
||||
91B622171B76C0A600B00E0F /* libpango-1.0.0.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242E18EF07B4001FA499 /* libpango-1.0.0.dylib */; };
|
||||
91B622181B76C0A600B00E0F /* libpangocairo-1.0.0.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242F18EF07B4001FA499 /* libpangocairo-1.0.0.dylib */; };
|
||||
91B622191B76C0A600B00E0F /* libpangoft2-1.0.0.dylib in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C243018EF07B4001FA499 /* libpangoft2-1.0.0.dylib */; };
|
||||
|
@ -1084,7 +1083,6 @@
|
|||
EC2194491960634100420969 /* game_launcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2194471960634100420969 /* game_launcher.cpp */; };
|
||||
EC21944F1960649000420969 /* display_chat_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC21944D1960649000420969 /* display_chat_manager.cpp */; };
|
||||
EC219454196064C500420969 /* wesnoth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC219453196064C500420969 /* wesnoth.cpp */; };
|
||||
EC225C9718EF0ABC00025EC4 /* libintl.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242D18EF07B4001FA499 /* libintl.8.dylib */; };
|
||||
EC2864301967989000168DFF /* filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC28642E1967988F00168DFF /* filter.cpp */; };
|
||||
EC2864331967990500168DFF /* variable_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC2864311967990500168DFF /* variable_info.cpp */; };
|
||||
EC28C58F19423426003B0F5E /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC28C58D19423426003B0F5E /* utils.cpp */; };
|
||||
|
@ -1142,7 +1140,6 @@
|
|||
EC5C244318EF07B4001FA499 /* libgthread-2.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242A18EF07B4001FA499 /* libgthread-2.0.0.dylib */; };
|
||||
EC5C244418EF07B4001FA499 /* libharfbuzz.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242B18EF07B4001FA499 /* libharfbuzz.0.dylib */; };
|
||||
EC5C244518EF07B4001FA499 /* libiconv.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242C18EF07B4001FA499 /* libiconv.2.dylib */; };
|
||||
EC5C244618EF07B4001FA499 /* libintl.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242D18EF07B4001FA499 /* libintl.8.dylib */; };
|
||||
EC5C244718EF07B4001FA499 /* libpango-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242E18EF07B4001FA499 /* libpango-1.0.0.dylib */; };
|
||||
EC5C244818EF07B4001FA499 /* libpangocairo-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C242F18EF07B4001FA499 /* libpangocairo-1.0.0.dylib */; };
|
||||
EC5C244918EF07B4001FA499 /* libpangoft2-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = EC5C243018EF07B4001FA499 /* libpangoft2-1.0.0.dylib */; };
|
||||
|
@ -1374,7 +1371,6 @@
|
|||
91B622131B76C0A600B00E0F /* libgthread-2.0.0.dylib in Copy Frameworks */,
|
||||
91B622141B76C0A600B00E0F /* libharfbuzz.0.dylib in Copy Frameworks */,
|
||||
91B622151B76C0A600B00E0F /* libiconv.2.dylib in Copy Frameworks */,
|
||||
91B622161B76C0A600B00E0F /* libintl.8.dylib in Copy Frameworks */,
|
||||
91B622171B76C0A600B00E0F /* libpango-1.0.0.dylib in Copy Frameworks */,
|
||||
91B622181B76C0A600B00E0F /* libpangocairo-1.0.0.dylib in Copy Frameworks */,
|
||||
91B622191B76C0A600B00E0F /* libpangoft2-1.0.0.dylib in Copy Frameworks */,
|
||||
|
@ -1728,6 +1724,16 @@
|
|||
91B621F41B76BCB000B00E0F /* ucs4_iterator_base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ucs4_iterator_base.hpp; sourceTree = "<group>"; };
|
||||
91B621F51B76BCB000B00E0F /* unicode_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_cast.hpp; sourceTree = "<group>"; };
|
||||
91B621F61B76BCB000B00E0F /* unicode_types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = unicode_types.hpp; sourceTree = "<group>"; };
|
||||
91BE78371DD8F5DE00528C21 /* catalog.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catalog.hpp; sourceTree = "<group>"; };
|
||||
91BE78381DD8F5DE00528C21 /* catalog_metadata.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = catalog_metadata.hpp; sourceTree = "<group>"; };
|
||||
91BE78391DD8F5DE00528C21 /* default_plural_forms_compiler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = default_plural_forms_compiler.hpp; sourceTree = "<group>"; };
|
||||
91BE783A1DD8F5DE00528C21 /* default_plural_forms_expressions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = default_plural_forms_expressions.hpp; sourceTree = "<group>"; };
|
||||
91BE783B1DD8F5DE00528C21 /* exceptions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = exceptions.hpp; sourceTree = "<group>"; };
|
||||
91BE783C1DD8F5DE00528C21 /* po_grammar.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = po_grammar.hpp; sourceTree = "<group>"; };
|
||||
91BE783D1DD8F5DE00528C21 /* po_message.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = po_message.hpp; sourceTree = "<group>"; };
|
||||
91BE783E1DD8F5DE00528C21 /* po_message_adapted.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = po_message_adapted.hpp; sourceTree = "<group>"; };
|
||||
91BE783F1DD8F5DE00528C21 /* version.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = version.hpp; sourceTree = "<group>"; };
|
||||
91BE78401DD8F5DE00528C21 /* spirit_po.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = spirit_po.hpp; sourceTree = "<group>"; };
|
||||
91C548CE1D8866ED00FE6A7B /* campaignd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = campaignd; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
91C548CF1D886AF000FE6A7B /* send_receive_wml_helpers.ipp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = send_receive_wml_helpers.ipp; sourceTree = "<group>"; };
|
||||
91C548D01D886AF000FE6A7B /* server_base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = server_base.cpp; sourceTree = "<group>"; };
|
||||
|
@ -2474,7 +2480,6 @@
|
|||
EC5C242A18EF07B4001FA499 /* libgthread-2.0.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libgthread-2.0.0.dylib"; path = "lib/libgthread-2.0.0.dylib"; sourceTree = "<group>"; };
|
||||
EC5C242B18EF07B4001FA499 /* libharfbuzz.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libharfbuzz.0.dylib; path = lib/libharfbuzz.0.dylib; sourceTree = "<group>"; };
|
||||
EC5C242C18EF07B4001FA499 /* libiconv.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.2.dylib; path = lib/libiconv.2.dylib; sourceTree = "<group>"; };
|
||||
EC5C242D18EF07B4001FA499 /* libintl.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libintl.8.dylib; path = lib/libintl.8.dylib; sourceTree = "<group>"; };
|
||||
EC5C242E18EF07B4001FA499 /* libpango-1.0.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libpango-1.0.0.dylib"; path = "lib/libpango-1.0.0.dylib"; sourceTree = "<group>"; };
|
||||
EC5C242F18EF07B4001FA499 /* libpangocairo-1.0.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libpangocairo-1.0.0.dylib"; path = "lib/libpangocairo-1.0.0.dylib"; sourceTree = "<group>"; };
|
||||
EC5C243018EF07B4001FA499 /* libpangoft2-1.0.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libpangoft2-1.0.0.dylib"; path = "lib/libpangoft2-1.0.0.dylib"; sourceTree = "<group>"; };
|
||||
|
@ -2735,7 +2740,6 @@
|
|||
EC5C244318EF07B4001FA499 /* libgthread-2.0.0.dylib in Frameworks */,
|
||||
EC5C244418EF07B4001FA499 /* libharfbuzz.0.dylib in Frameworks */,
|
||||
EC5C244518EF07B4001FA499 /* libiconv.2.dylib in Frameworks */,
|
||||
EC5C244618EF07B4001FA499 /* libintl.8.dylib in Frameworks */,
|
||||
EC5C244718EF07B4001FA499 /* libpango-1.0.0.dylib in Frameworks */,
|
||||
EC5C244818EF07B4001FA499 /* libpangocairo-1.0.0.dylib in Frameworks */,
|
||||
90606A2B1D5599BA00719B40 /* libpcre.1.dylib in Frameworks */,
|
||||
|
@ -2809,7 +2813,6 @@
|
|||
EC4DF45B19FEA84A000EC086 /* libboost_locale-mt.dylib in Frameworks */,
|
||||
EC64D7681A085F4A0092EF75 /* libboost_random-mt.dylib in Frameworks */,
|
||||
ECF9D44219F4050E00E6C9D9 /* libboost_system-mt.dylib in Frameworks */,
|
||||
EC225C9718EF0ABC00025EC4 /* libintl.8.dylib in Frameworks */,
|
||||
91C554681D77A545002DB0C8 /* libpcre.1.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -3102,6 +3105,8 @@
|
|||
B52EE8C5121359A600CFBDAB /* sound_music_track.hpp */,
|
||||
B559999D0EC62181008DD061 /* soundsource.cpp */,
|
||||
B559999C0EC62181008DD061 /* soundsource.hpp */,
|
||||
91BE78361DD8F5DE00528C21 /* spirit_po */,
|
||||
91BE78401DD8F5DE00528C21 /* spirit_po.hpp */,
|
||||
B559999B0EC62181008DD061 /* statistics.cpp */,
|
||||
B559999A0EC62181008DD061 /* statistics.hpp */,
|
||||
B55999990EC62181008DD061 /* statistics_dialog.cpp */,
|
||||
|
@ -3207,7 +3212,6 @@
|
|||
EC5C242A18EF07B4001FA499 /* libgthread-2.0.0.dylib */,
|
||||
EC5C242B18EF07B4001FA499 /* libharfbuzz.0.dylib */,
|
||||
EC5C242C18EF07B4001FA499 /* libiconv.2.dylib */,
|
||||
EC5C242D18EF07B4001FA499 /* libintl.8.dylib */,
|
||||
EC5C242E18EF07B4001FA499 /* libpango-1.0.0.dylib */,
|
||||
EC5C242F18EF07B4001FA499 /* libpangocairo-1.0.0.dylib */,
|
||||
EC5C243018EF07B4001FA499 /* libpangoft2-1.0.0.dylib */,
|
||||
|
@ -3611,6 +3615,22 @@
|
|||
path = xBRZ;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
91BE78361DD8F5DE00528C21 /* spirit_po */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
91BE78371DD8F5DE00528C21 /* catalog.hpp */,
|
||||
91BE78381DD8F5DE00528C21 /* catalog_metadata.hpp */,
|
||||
91BE78391DD8F5DE00528C21 /* default_plural_forms_compiler.hpp */,
|
||||
91BE783A1DD8F5DE00528C21 /* default_plural_forms_expressions.hpp */,
|
||||
91BE783B1DD8F5DE00528C21 /* exceptions.hpp */,
|
||||
91BE783C1DD8F5DE00528C21 /* po_grammar.hpp */,
|
||||
91BE783D1DD8F5DE00528C21 /* po_message.hpp */,
|
||||
91BE783E1DD8F5DE00528C21 /* po_message_adapted.hpp */,
|
||||
91BE783F1DD8F5DE00528C21 /* version.hpp */,
|
||||
);
|
||||
path = spirit_po;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
91EF6BF01C9E217C00E2A733 /* utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
|
@ -57,41 +57,4 @@ def generate(env):
|
|||
)
|
||||
env["BUILDERS"]["Po4aTranslate"] = po4a_translate
|
||||
|
||||
def CheckGettextLibintl(context):
|
||||
env = context.env
|
||||
backup = env.Clone().Dictionary()
|
||||
context.Message("Checking for Gettext's libintl... ")
|
||||
|
||||
test_program = """
|
||||
#include <libintl.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
textdomain("test");
|
||||
char* text = gettext("foo");
|
||||
}
|
||||
\n"""
|
||||
|
||||
if not env.get("gettextdir") and context.TryLink(test_program, ".c"):
|
||||
context.Result("libc built-in")
|
||||
return True
|
||||
|
||||
prefixes = [env["prefix"]]
|
||||
if env.get("gettextdir"):
|
||||
prefixes = [env["gettextdir"]] + prefixes
|
||||
includes = find_include(prefixes, "libintl.h", default_prefixes=not env["host"])
|
||||
if includes:
|
||||
env.AppendUnique(
|
||||
CPPPATH = [join(includes[0][0], "include")],
|
||||
LIBPATH = [join(includes[0][0], "lib")]
|
||||
)
|
||||
env.AppendUnique(LIBS = ["intl"])
|
||||
if context.TryLink("/* external libintl*/\n" + test_program, ".c"):
|
||||
context.Result("external")
|
||||
return True
|
||||
|
||||
context.Result("no")
|
||||
env.Replace(**backup)
|
||||
return False
|
||||
|
||||
config_checks = { "CheckGettextLibintl" : CheckGettextLibintl }
|
||||
config_checks = {}
|
||||
|
|
|
@ -18,7 +18,6 @@ endif(MSVC)
|
|||
include_directories(SYSTEM ${PANGOCAIRO_INCLUDE_DIRS} )
|
||||
include_directories(SYSTEM ${GETTEXT_INCLUDE_DIR} )
|
||||
include_directories(SYSTEM ${LIBDBUS_INCLUDE_DIRS} )
|
||||
include_directories(SYSTEM ${LIBINTL_INCLUDE_DIR} )
|
||||
include_directories(SYSTEM ${VORBISFILE_INCLUDE_DIR} )
|
||||
|
||||
|
||||
|
@ -78,17 +77,10 @@ set(common-external-libs
|
|||
${Boost_FILESYSTEM_LIBRARY}
|
||||
)
|
||||
|
||||
if(NOT ENABLE_LIBINTL)
|
||||
set(common-external-libs
|
||||
${common-external-libs}
|
||||
${Boost_LOCALE_LIBRARY}
|
||||
)
|
||||
else()
|
||||
set(common-external-libs
|
||||
${common-external-libs}
|
||||
${LIBINTL_LIBRARY}
|
||||
)
|
||||
endif()
|
||||
set(common-external-libs
|
||||
${common-external-libs}
|
||||
${Boost_LOCALE_LIBRARY}
|
||||
)
|
||||
|
||||
set(game-external-libs
|
||||
${common-external-libs}
|
||||
|
@ -222,6 +214,7 @@ set(libwesnoth-core_STAT_SRC
|
|||
filesystem_common.cpp
|
||||
font/constants.cpp
|
||||
game_config.cpp
|
||||
gettext_boost.cpp
|
||||
hash.cpp
|
||||
log.cpp
|
||||
md5.cpp
|
||||
|
@ -239,18 +232,6 @@ set(libwesnoth-core_STAT_SRC
|
|||
serialization/validator.cpp
|
||||
)
|
||||
|
||||
if(NOT ENABLE_LIBINTL)
|
||||
set(libwesnoth-core_STAT_SRC
|
||||
${libwesnoth-core_STAT_SRC}
|
||||
gettext_boost.cpp
|
||||
)
|
||||
else()
|
||||
set(libwesnoth-core_STAT_SRC
|
||||
${libwesnoth-core_STAT_SRC}
|
||||
gettext.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(libwesnoth-core_STAT_SRC
|
||||
${libwesnoth-core_STAT_SRC}
|
||||
|
@ -1185,7 +1166,6 @@ add_executable(campaignd ${campaignd_SRC})
|
|||
target_link_libraries(campaignd
|
||||
wesnoth-core
|
||||
${server-external-libs}
|
||||
${LIBINTL_LIBRARIES}
|
||||
${Boost_RANDOM_LIBRARY}
|
||||
${LIBRT}
|
||||
)
|
||||
|
|
|
@ -75,14 +75,9 @@ if env["PLATFORM"] == "win32":
|
|||
filesystem_env.Object("log_windows.cpp")
|
||||
])
|
||||
|
||||
if env["libintl"]:
|
||||
libwesnoth_core_sources.extend([
|
||||
filesystem_env.Object("gettext.cpp")
|
||||
])
|
||||
else:
|
||||
libwesnoth_core_sources.extend([
|
||||
filesystem_env.Object("gettext_boost.cpp")
|
||||
])
|
||||
libwesnoth_core_sources.extend([
|
||||
filesystem_env.Object("gettext_boost.cpp")
|
||||
])
|
||||
|
||||
libwesnoth_core = [env.Library("wesnoth_core", libwesnoth_core_sources)]
|
||||
|
||||
|
|
|
@ -586,6 +586,7 @@ void set_user_data_dir(std::string newprefdir)
|
|||
|
||||
#endif /*_WIN32*/
|
||||
setup_user_data_dir();
|
||||
user_data_dir = normalize_path(user_data_dir.string(), true, true);
|
||||
}
|
||||
|
||||
static void set_user_config_path(path newconfig)
|
||||
|
|
196
src/gettext.cpp
196
src/gettext.cpp
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2016 by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "global.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libintl.h>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef setlocale
|
||||
// Someone in libintl world decided it was a good idea to define a "setlocale" macro.
|
||||
// Note: This is necessary to compile on OS X, it fixes bug #16649
|
||||
#undef setlocale
|
||||
#endif
|
||||
|
||||
#define DBG_G LOG_STREAM(debug, lg::general)
|
||||
#define LOG_G LOG_STREAM(info, lg::general)
|
||||
#define WRN_G LOG_STREAM(warn, lg::general)
|
||||
#define ERR_G LOG_STREAM(err, lg::general)
|
||||
namespace translation
|
||||
{
|
||||
std::string dgettext(const char* domain, const char* msgid)
|
||||
{
|
||||
return ::dgettext(domain, msgid);
|
||||
}
|
||||
std::string egettext(char const *msgid)
|
||||
{
|
||||
return msgid[0] == '\0' ? msgid : (::gettext)(msgid);
|
||||
}
|
||||
|
||||
std::string dsgettext (const char * domainname, const char *msgid)
|
||||
{
|
||||
bind_textdomain_codeset(domainname, "UTF-8");
|
||||
const char *msgval = ::dgettext (domainname, msgid);
|
||||
if (msgval == msgid) {
|
||||
msgval = std::strrchr (msgid, '^');
|
||||
if (msgval == nullptr)
|
||||
msgval = msgid;
|
||||
else
|
||||
msgval++;
|
||||
}
|
||||
return msgval;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
const char* sgettext (const char *msgid)
|
||||
{
|
||||
const char *msgval = gettext (msgid);
|
||||
if (msgval == msgid) {
|
||||
msgval = std::strrchr (msgid, '^');
|
||||
if (msgval == nullptr)
|
||||
msgval = msgid;
|
||||
else
|
||||
msgval++;
|
||||
}
|
||||
return msgval;
|
||||
}
|
||||
|
||||
const char* sngettext (const char *singular, const char *plural, int n)
|
||||
{
|
||||
const char *msgval = ngettext (singular, plural, n);
|
||||
if (msgval == singular) {
|
||||
msgval = std::strrchr (singular, '^');
|
||||
if (msgval == nullptr)
|
||||
msgval = singular;
|
||||
else
|
||||
msgval++;
|
||||
}
|
||||
return msgval;
|
||||
}
|
||||
|
||||
#endif
|
||||
std::string dsngettext (const char * domainname, const char *singular, const char *plural, int n)
|
||||
{
|
||||
bind_textdomain_codeset(domainname, "UTF-8");
|
||||
const char *msgval = ::dngettext (domainname, singular, plural, n);
|
||||
if (msgval == singular) {
|
||||
msgval = std::strrchr (singular, '^');
|
||||
if (msgval == nullptr)
|
||||
msgval = singular;
|
||||
else
|
||||
msgval++;
|
||||
}
|
||||
return msgval;
|
||||
}
|
||||
|
||||
void bind_textdomain(const char* domain, const char* directory, const char* encoding)
|
||||
{
|
||||
if(domain != nullptr && strchr(domain, '/') != nullptr) {
|
||||
// For compatibility with Boost.Locale implementation, which interprets
|
||||
// slashes in domain names in a special fashion.
|
||||
ERR_G << "illegal textdomain name '" << domain
|
||||
<< "', skipping textdomain\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if(directory != nullptr)
|
||||
bindtextdomain(domain, directory);
|
||||
if(encoding != nullptr)
|
||||
bind_textdomain_codeset(domain, encoding);
|
||||
}
|
||||
|
||||
void set_default_textdomain(const char* domain)
|
||||
{
|
||||
textdomain(domain);
|
||||
}
|
||||
|
||||
void set_language(const std::string& slocale, const std::vector<std::string>* alternates)
|
||||
{
|
||||
|
||||
//Code copied from language.cpp::wesnoth_setlocale()
|
||||
std::string locale = slocale;
|
||||
// FIXME: ideally we should check LANGUAGE and on first invocation
|
||||
// use that value, so someone with es would get the game in Spanish
|
||||
// instead of en_US the first time round
|
||||
// LANGUAGE overrides other settings, so for now just get rid of it
|
||||
|
||||
#ifdef _WIN32
|
||||
(void)alternates;
|
||||
std::string win_locale(locale, 0, 2);
|
||||
#include "language_win32.ii"
|
||||
SetEnvironmentVariableA("LANG", win_locale.c_str());
|
||||
std::string env = "LANGUAGE=" + locale;
|
||||
_putenv(env.c_str());
|
||||
return;
|
||||
#else
|
||||
// FIXME: add configure check for unsetenv
|
||||
unsetenv ("LANGUAGE"); // void so no return value to check
|
||||
#ifdef __APPLE__
|
||||
if (setenv("LANG", locale.c_str(), 1) == -1) {
|
||||
ERR_G << "setenv LANG failed: " << strerror(errno);
|
||||
}
|
||||
#endif
|
||||
|
||||
char *res = nullptr;
|
||||
std::vector<std::string>::const_iterator i;
|
||||
if (alternates) i = alternates->begin();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::string lang = locale, extra;
|
||||
std::string::size_type pos = locale.find('@');
|
||||
if (pos != std::string::npos) {
|
||||
lang.erase(pos);
|
||||
extra = locale.substr(pos);
|
||||
}
|
||||
|
||||
/*
|
||||
* The "" is the last item to work-around a problem in glibc picking
|
||||
* the non utf8 locale instead an utf8 version if available.
|
||||
*/
|
||||
char const *encoding[] = { ".utf-8", ".UTF-8", "" };
|
||||
for (int j = 0; j != 3; ++j)
|
||||
{
|
||||
locale = lang + encoding[j] + extra;
|
||||
res = std::setlocale(LC_MESSAGES, locale.c_str());
|
||||
if (res) {
|
||||
LOG_G << "Set locale to '" << locale << "' result: '" << res << "'.\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alternates || i == alternates->end()) break;
|
||||
locale = *i;
|
||||
++i;
|
||||
}
|
||||
WRN_G << "setlocale() failed for '" << slocale << "'." << std::endl;
|
||||
#endif //win32
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
#ifndef _WIN32
|
||||
std::setlocale(LC_MESSAGES, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
|
@ -15,14 +15,25 @@
|
|||
#include "global.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <boost/locale.hpp>
|
||||
// including boost/thread fixes linking of boost locale for msvc on boost 1.60
|
||||
#include <boost/thread.hpp>
|
||||
#include <set>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#endif
|
||||
#include "spirit_po.hpp"
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#define DBG_G LOG_STREAM(debug, lg::general())
|
||||
#define LOG_G LOG_STREAM(info, lg::general())
|
||||
#define WRN_G LOG_STREAM(warn, lg::general())
|
||||
|
@ -49,7 +60,7 @@ namespace
|
|||
{
|
||||
//NOTE: the default_locale objects needs to live as least as long as the locale_info object. Otherwise the programm will segfault.
|
||||
std::locale default_locale = bl::generator().generate("");
|
||||
const boost::locale::info& locale_info = std::use_facet< boost::locale::info >(default_locale);
|
||||
const bl::info& locale_info = std::use_facet<bl::info>(default_locale);
|
||||
name_ += locale_info.language();
|
||||
if(!locale_info.country().empty())
|
||||
name_ += "_" + locale_info.country();
|
||||
|
@ -66,6 +77,135 @@ namespace
|
|||
|
||||
std::string name_;
|
||||
};
|
||||
class wesnoth_message_format : public bl::message_format<char>
|
||||
{
|
||||
using po_catalog = spirit_po::catalog<>;
|
||||
public:
|
||||
wesnoth_message_format(std::locale base, const std::set<std::string>& domains, const std::set<std::string>& paths)
|
||||
: base_loc_(base)
|
||||
{
|
||||
const bl::info& inf = std::use_facet<bl::info>(base);
|
||||
if(inf.language() == "c") {
|
||||
return;
|
||||
}
|
||||
std::string lang_name_short = inf.language();
|
||||
std::string lang_name_long = lang_name_short;
|
||||
if(!inf.country().empty()) {
|
||||
lang_name_long += '_';
|
||||
lang_name_long += inf.country();
|
||||
}
|
||||
if(!inf.variant().empty()) {
|
||||
lang_name_long += '@';
|
||||
lang_name_long += inf.variant();
|
||||
lang_name_short += '@';
|
||||
lang_name_short += inf.variant();
|
||||
}
|
||||
DBG_G << "Loading po files for language " << lang_name_long << '\n';
|
||||
for(auto& domain : domains) {
|
||||
DBG_G << "Searching for po files for domain " << domain << '\n';
|
||||
std::string path;
|
||||
for(auto base_path : paths) {
|
||||
DBG_G << "Searching in dir " << base_path << '\n';
|
||||
if(base_path[base_path.length()-1] != '/') {
|
||||
base_path += '/';
|
||||
}
|
||||
base_path += domain;
|
||||
base_path += '/';
|
||||
path = base_path + lang_name_long + ".po";
|
||||
DBG_G << " Trying path " << path << '\n';
|
||||
if(filesystem::file_exists(path)) {
|
||||
break;
|
||||
}
|
||||
path = base_path + lang_name_short + ".po";
|
||||
DBG_G << " Trying path " << path << '\n';
|
||||
if(filesystem::file_exists(path)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!filesystem::file_exists(path)) {
|
||||
continue;
|
||||
}
|
||||
std::ifstream po_file;
|
||||
po_file.exceptions(std::ios::badbit);
|
||||
LOG_G << "Loading language file from " << path << '\n';
|
||||
try {
|
||||
po_file.open(path);
|
||||
const po_catalog& cat = po_catalog::from_istream(po_file);
|
||||
extra_messages_.emplace(get_base().domain(domain), cat);
|
||||
} catch(spirit_po::catalog_exception& e) {
|
||||
throw_po_error(lang_name_long, domain, e.what());
|
||||
} catch(std::ios::failure& e) {
|
||||
throw_po_error(lang_name_long, domain, strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NORETURN static void throw_po_error(const std::string& lang, const std::string& dom, const std::string& detail) {
|
||||
std::ostringstream err;
|
||||
err << "Error opening language file for " << lang << ", textdomain " << dom
|
||||
<< ":\n " << detail << '\n';
|
||||
ERR_G << err.rdbuf() << std::flush;
|
||||
throw game::error(err.str());
|
||||
}
|
||||
|
||||
const char* get(int domain_id, const char* ctx, const char* id) const override
|
||||
{
|
||||
auto& base = get_base();
|
||||
const char* msg = base.get(domain_id, ctx, id);
|
||||
if(msg == nullptr) {
|
||||
auto iter = extra_messages_.find(domain_id);
|
||||
if(iter == extra_messages_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& catalog = iter->second;
|
||||
const char* lookup = ctx ? catalog.pgettext(ctx, id) : catalog.gettext(id);
|
||||
if(lookup != id) {
|
||||
// (p)gettext returns the input pointer if the string was not found
|
||||
msg = lookup;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
const char* get(int domain_id, const char* ctx, const char* sid, int n) const override
|
||||
{
|
||||
auto& base = get_base();
|
||||
const char* msg = base.get(domain_id, ctx, sid, n);
|
||||
if(msg == nullptr) {
|
||||
auto iter = extra_messages_.find(domain_id);
|
||||
if(iter == extra_messages_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& catalog = iter->second;
|
||||
const char* lookup = ctx ? catalog.npgettext(ctx, sid, sid, n) : catalog.ngettext(sid, sid, n);
|
||||
if(lookup != sid) {
|
||||
// n(p)gettext returns one of the input pointers if the string was not found
|
||||
msg = lookup;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
int domain(const std::string& domain) const override
|
||||
{
|
||||
auto& base = get_base();
|
||||
return base.domain(domain);
|
||||
}
|
||||
|
||||
const char* convert(const char* msg, std::string& buffer) const override
|
||||
{
|
||||
auto& base = get_base();
|
||||
return base.convert(msg, buffer);
|
||||
}
|
||||
private:
|
||||
const bl::message_format<char>& get_base() const
|
||||
{
|
||||
return std::use_facet<bl::message_format<char>>(base_loc_);
|
||||
}
|
||||
|
||||
std::locale base_loc_;
|
||||
std::map<int, po_catalog> extra_messages_;
|
||||
};
|
||||
struct translation_manager
|
||||
{
|
||||
translation_manager()
|
||||
|
@ -85,9 +225,9 @@ namespace
|
|||
generator_.use_ansi_encoding(false);
|
||||
generator_.categories(bl::message_facet | bl::information_facet | bl::collation_facet | bl::formatting_facet);
|
||||
generator_.characters(bl::char_facet);
|
||||
//we cannot have current_locale_ beeing a non boost gerenerated locale since it might not suppy
|
||||
//the boost::locale::info facet. as soon as we add message paths update_locale_internal might fail
|
||||
//for example becasue of invalid .mo files. So make sure we call it at least once before adding paths/domains
|
||||
// We cannot have current_locale_ be a non boost-generated locale since it might not supply
|
||||
// the bl::info facet. As soon as we add message paths, update_locale_internal might fail,
|
||||
// for example because of invalid .mo files. So make sure we call it at least once before adding paths/domains
|
||||
update_locale_internal();
|
||||
}
|
||||
|
||||
|
@ -158,7 +298,8 @@ namespace
|
|||
{
|
||||
LOG_G << "attempting to generate locale by name '" << current_language_ << "'\n";
|
||||
current_locale_ = generator_.generate(current_language_);
|
||||
const boost::locale::info& info = std::use_facet< boost::locale::info >(current_locale_);
|
||||
current_locale_ = std::locale(current_locale_, new wesnoth_message_format(current_locale_, loaded_domains_, loaded_paths_));
|
||||
const bl::info& info = std::use_facet<bl::info>(current_locale_);
|
||||
LOG_G << "updated locale to '" << current_language_ << "' locale is now '" << current_locale_.name() << "' ( "
|
||||
<< "name='" << info.name()
|
||||
<< "' country='" << info.country()
|
||||
|
@ -166,10 +307,10 @@ namespace
|
|||
<< "' encoding='" << info.encoding()
|
||||
<< "' variant='" << info.variant() << "')\n";
|
||||
}
|
||||
catch(const boost::locale::conv::conversion_error&)
|
||||
catch(const bl::conv::conversion_error&)
|
||||
{
|
||||
assert(std::has_facet<boost::locale::info>(current_locale_));
|
||||
const boost::locale::info& info = std::use_facet< boost::locale::info >(current_locale_);
|
||||
assert(std::has_facet<bl::info>(current_locale_));
|
||||
const bl::info& info = std::use_facet<bl::info>(current_locale_);
|
||||
ERR_G << "Failed to update locale due to conversion error, locale is now: "
|
||||
<< "name='" << info.name()
|
||||
<< "' country='" << info.country()
|
||||
|
@ -194,7 +335,7 @@ namespace
|
|||
std::set<std::string> loaded_paths_;
|
||||
std::set<std::string> loaded_domains_;
|
||||
std::string current_language_;
|
||||
boost::locale::generator generator_;
|
||||
bl::generator generator_;
|
||||
std::locale current_locale_;
|
||||
bool is_dirty_;
|
||||
};
|
||||
|
@ -212,11 +353,11 @@ namespace translation
|
|||
|
||||
std::string dgettext(const char* domain, const char* msgid)
|
||||
{
|
||||
return boost::locale::dgettext(domain, msgid, get_manager().get_locale());
|
||||
return bl::dgettext(domain, msgid, get_manager().get_locale());
|
||||
}
|
||||
std::string egettext(char const *msgid)
|
||||
{
|
||||
return msgid[0] == '\0' ? msgid : boost::locale::gettext(msgid, get_manager().get_locale());
|
||||
return msgid[0] == '\0' ? msgid : bl::gettext(msgid, get_manager().get_locale());
|
||||
}
|
||||
|
||||
std::string dsgettext (const char * domainname, const char *msgid)
|
||||
|
@ -234,7 +375,7 @@ std::string dsgettext (const char * domainname, const char *msgid)
|
|||
|
||||
std::string dsngettext (const char * domainname, const char *singular, const char *plural, int n)
|
||||
{
|
||||
std::string msgval = boost::locale::dngettext(domainname, singular, plural, n, get_manager().get_locale());
|
||||
std::string msgval = bl::dngettext(domainname, singular, plural, n, get_manager().get_locale());
|
||||
if (msgval == singular) {
|
||||
const char* firsthat = std::strrchr (singular, '^');
|
||||
if (firsthat == nullptr)
|
||||
|
|
|
@ -99,9 +99,10 @@ logger& debug()
|
|||
return lg;
|
||||
}
|
||||
|
||||
static log_domain dom("general");
|
||||
|
||||
log_domain& general()
|
||||
{
|
||||
static log_domain dom("general");
|
||||
return dom;
|
||||
}
|
||||
|
||||
|
|
12
src/spirit_po.hpp
Normal file
12
src/spirit_po.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_HPP_INCLUDED
|
||||
#define SPIRIT_PO_HPP_INCLUDED
|
||||
|
||||
#include <spirit_po/version.hpp>
|
||||
#include <spirit_po/catalog.hpp>
|
||||
|
||||
#endif // SPIRIT_PO_HPP_INCLUDED
|
420
src/spirit_po/catalog.hpp
Normal file
420
src/spirit_po/catalog.hpp
Normal file
|
@ -0,0 +1,420 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_CATALOG_HPP_INCLUDED
|
||||
#define SPIRIT_PO_CATALOG_HPP_INCLUDED
|
||||
|
||||
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#define BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#endif
|
||||
|
||||
#include <spirit_po/catalog_metadata.hpp>
|
||||
#include <spirit_po/default_plural_forms_compiler.hpp>
|
||||
#include <spirit_po/exceptions.hpp>
|
||||
#include <spirit_po/po_grammar.hpp>
|
||||
#include <spirit_po/po_message.hpp>
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <functional>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
namespace spirit = boost::spirit;
|
||||
namespace qi = spirit::qi;
|
||||
typedef unsigned int uint;
|
||||
|
||||
typedef std::function<void(const std::string &)> warning_channel_type;
|
||||
typedef std::unordered_map<std::string, po_message> default_hashmap_type;
|
||||
|
||||
template <typename hashmap_type = default_hashmap_type, typename pf_compiler = default_plural_forms::compiler>
|
||||
class catalog {
|
||||
catalog_metadata metadata_;
|
||||
|
||||
typename pf_compiler::result_type pf_function_object_;
|
||||
uint singular_index_; // cached result of pf_function_object(1)
|
||||
|
||||
#ifdef SPIRIT_PO_NOEXCEPT
|
||||
boost::optional<std::string> error_message_;
|
||||
// if loading failed, error_message_ contains an error
|
||||
// (rather than throwing an exception)
|
||||
#endif // SPIRIT_PO_NOEXCEPT
|
||||
warning_channel_type warning_channel_;
|
||||
|
||||
hashmap_type hashmap_;
|
||||
|
||||
public:
|
||||
static constexpr char EOT = static_cast<char>(4);
|
||||
// ASCII 4 is EOT character
|
||||
// Used to separate msg context from msgid in the hashmap, in MO files
|
||||
// We use the same formatting system, just for consistency.
|
||||
// c.f. https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
|
||||
static std::string form_context_index(const std::string & msgctxt, const std::string & id) {
|
||||
return msgctxt + EOT + id;
|
||||
}
|
||||
|
||||
static std::string form_index(const po_message & msg) {
|
||||
return msg.context ? form_context_index(*msg.context, msg.id) : msg.id;
|
||||
}
|
||||
|
||||
private:
|
||||
/***
|
||||
* Helper for interacting with hashmap results
|
||||
* get(msg) gets the *singular* string from the message. if it's a plural message, look in singular_index_.
|
||||
* if it's not a plural message, then there is only one string. also, the po header is never a plural message
|
||||
*/
|
||||
const std::string & get(const po_message & msg) const {
|
||||
if (msg.strings().size() == 1) { return msg.strings()[0]; }
|
||||
return msg.strings()[singular_index_];
|
||||
}
|
||||
|
||||
const std::string & get(const po_message & msg, uint plural) const {
|
||||
uint idx = (plural == 1 ? singular_index_ : pf_function_object_(plural));
|
||||
return msg.strings()[idx];
|
||||
}
|
||||
|
||||
/***
|
||||
* Emplace a message into the hashmap
|
||||
*/
|
||||
void insert_message(po_message && msg) {
|
||||
if (!msg.strings().size()) { return; }
|
||||
// don't allow messages with ZERO translations into the catalog, this will cause segfaults later.
|
||||
// should perhaps throw an exception here
|
||||
|
||||
if (!msg.strings()[0].size()) { return; }
|
||||
// if the (first) translated string is "", it is untranslated and message does not enter catalog
|
||||
|
||||
if (msg.strings().size() > 1 && msg.strings().size() != metadata_.num_plural_forms) {
|
||||
if (warning_channel_) {
|
||||
warning_channel_("Ignoring a message with an incorrect number of plural forms: plural = " + std::to_string(msg.strings().size()) + " msgid = '" + msg.id + "'");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::string index = form_index(msg);
|
||||
// adjust the id based on context if necessary
|
||||
|
||||
auto result = hashmap_.emplace(std::move(index), std::move(msg));
|
||||
|
||||
// Issue a warning if emplace failed, rather than silently overwrite.
|
||||
if (!result.second) {
|
||||
if (warning_channel_) {
|
||||
std::string warning = "Overwriting a message: msgid = <<<" + msg.id + ">>>";
|
||||
if (msg.context) { warning += " msgctxt = <<<" + *msg.context + ">>>"; }
|
||||
warning_channel_(warning);
|
||||
}
|
||||
result.first->second = std::move(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef SPIRIT_PO_NOEXCEPT
|
||||
/***
|
||||
* Error checking (this is done so we don't have to throw exceptions from the ctor.
|
||||
*/
|
||||
explicit operator bool() const {
|
||||
return !error_message_;
|
||||
}
|
||||
|
||||
std::string error() const {
|
||||
return *error_message_; // UB if there there is not an error message
|
||||
}
|
||||
#endif // SPIRIT_PO_NOEXCEPT
|
||||
|
||||
/***
|
||||
* Ctors
|
||||
*/
|
||||
template <typename Iterator>
|
||||
catalog(spirit::line_pos_iterator<Iterator> & it, spirit::line_pos_iterator<Iterator> & end, warning_channel_type warn_channel = warning_channel_type(), pf_compiler compiler = pf_compiler())
|
||||
: metadata_()
|
||||
, pf_function_object_()
|
||||
, warning_channel_(warn_channel)
|
||||
, hashmap_()
|
||||
{
|
||||
typedef spirit::line_pos_iterator<Iterator> iterator_type;
|
||||
po_grammar<iterator_type> grammar;
|
||||
|
||||
po_message msg;
|
||||
std::size_t line_no = 0;
|
||||
|
||||
// Parse header first
|
||||
{
|
||||
// must be able to parse first message
|
||||
qi::parse(it, end, grammar.skipped_block); // first parse any comments
|
||||
if (!qi::parse(it, end, grammar, msg)) { // now parse the main grammar target
|
||||
int err_line = it.position();
|
||||
SPIRIT_PO_CATALOG_FAIL("Failed to parse po header, stopped at line " + std::to_string(err_line) + ": " + iterator_context(it, end));
|
||||
}
|
||||
|
||||
// first message must have empty MSGID (po format says so)
|
||||
if (msg.id.size()) {
|
||||
SPIRIT_PO_CATALOG_FAIL("Failed to parse po header, first msgid must be empty string \"\", found: " + msg.id);
|
||||
}
|
||||
|
||||
// Now parse the header string itself
|
||||
if (msg.strings().size()) {
|
||||
std::string maybe_error = metadata_.parse_header(msg.strings()[0]);
|
||||
if (maybe_error.size()) {
|
||||
SPIRIT_PO_CATALOG_FAIL("Failed to parse po header: " + maybe_error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata_.num_plural_forms) {
|
||||
SPIRIT_PO_CATALOG_FAIL("Invalid metadata in po header, found num_plurals = 0");
|
||||
}
|
||||
|
||||
// Try to compile the plural forms function string
|
||||
pf_function_object_ = compiler(metadata_.plural_forms_function_string);
|
||||
if (!pf_function_object_) {
|
||||
SPIRIT_PO_CATALOG_FAIL(("Failed to read plural forms function. "
|
||||
"Input: '" + metadata_.plural_forms_function_string + "', "
|
||||
"error message: " + pf_function_object_.error()));
|
||||
}
|
||||
|
||||
// Cache the 'singular' form index since it is most common
|
||||
singular_index_ = pf_function_object_(1);
|
||||
if (singular_index_ >= metadata_.num_plural_forms) {
|
||||
SPIRIT_PO_CATALOG_FAIL(("Invalid plural forms function. "
|
||||
"On input n = 1, returned plural = " + std::to_string(singular_index_) + ", "
|
||||
"while num_plurals = " + std::to_string(metadata_.num_plural_forms)));
|
||||
}
|
||||
|
||||
msg.line_no = line_no;
|
||||
insert_message(std::move(msg)); // for compatibility, need to insert the header message at msgid ""
|
||||
}
|
||||
|
||||
// Now parse non-fuzzy messages
|
||||
while (it != end) {
|
||||
// this parse rule cannot fail, it can be a zero length match
|
||||
qi::parse(it, end, grammar.ignored_comments);
|
||||
|
||||
bool fuzzy = false;
|
||||
// this parse rule cannot fail, it can be a zero length match
|
||||
qi::parse(it, end, grammar.message_preamble, fuzzy);
|
||||
|
||||
// check if we exhausted the file by comments
|
||||
if (it != end) {
|
||||
msg = po_message{};
|
||||
msg.strings().reserve(metadata_.num_plural_forms); // try to prevent frequent vector reallocations
|
||||
line_no = it.position();
|
||||
// actually parse a message
|
||||
if (!qi::parse(it, end, grammar, msg)) {
|
||||
int err_line = it.position();
|
||||
SPIRIT_PO_CATALOG_FAIL(("Failed to parse po file, "
|
||||
"started at " + std::to_string(line_no) + ": , stopped at " + std::to_string(err_line) + ":\n"
|
||||
+ iterator_context(it, end)));
|
||||
}
|
||||
// cannot overwrite header
|
||||
if (!msg.id.size()) {
|
||||
int err_line = it.position();
|
||||
SPIRIT_PO_CATALOG_FAIL(("Malformed po file: Cannot overwrite the header entry later in the po file."
|
||||
"Started at " + std::to_string(line_no) + ": , stopped at " + std::to_string(err_line) + ":\n"
|
||||
+ iterator_context(it, end)));
|
||||
}
|
||||
msg.line_no = line_no;
|
||||
// only insert it if it wasn't marked fuzzy
|
||||
if (!fuzzy) { insert_message(std::move(msg)); }
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SPIRIT_PO_DEBUG
|
||||
// validate resulting hashmap
|
||||
for (const auto & p : hashmap_) {
|
||||
if (!p.second.strings().size()) { SPIRIT_PO_CATALOG_FAIL(("Internal catalog error: found a message id with no strings, msgid='" + p.first + "'")); }
|
||||
if (p.second.strings().size() != 1 && p.second.strings().size() != metadata_.num_plural_forms) {
|
||||
SPIRIT_PO_CATALOG_FAIL(("Internal catalog error: found a message id with wrong number of strings, msgid='" + p.first + "' num msgstr = " + std::to_string(p.second.strings().size()) + ", catalog num_plural_forms = " + std::to_string(metadata_.num_plural_forms) + "\nWhole message: " + debug_string(p.second) ));
|
||||
}
|
||||
}
|
||||
#endif // SPIRIT_PO_DEBUG
|
||||
}
|
||||
|
||||
// Upgrade an iterator pair to spirit::line_pos_iterators
|
||||
template <typename Iterator>
|
||||
static catalog from_iterators(Iterator & b, Iterator & e, warning_channel_type w = warning_channel_type()) {
|
||||
spirit::line_pos_iterator<Iterator> it{b};
|
||||
spirit::line_pos_iterator<Iterator> end{e};
|
||||
return catalog(it, end, w);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static catalog from_iterators(spirit::line_pos_iterator<Iterator> & b, spirit::line_pos_iterator<Iterator> & e, warning_channel_type w = warning_channel_type()) {
|
||||
return catalog(b, e, w);
|
||||
}
|
||||
|
||||
// Construct a catalog from a range using one expression
|
||||
template <typename Range>
|
||||
static catalog from_range(const Range & range, warning_channel_type w = warning_channel_type()) {
|
||||
auto it = boost::begin(range);
|
||||
auto end = boost::end(range);
|
||||
return from_iterators(it, end, w);
|
||||
}
|
||||
|
||||
static catalog from_istream(std::istream & is, warning_channel_type w = warning_channel_type()) {
|
||||
// no white space skipping in the stream!
|
||||
is.unsetf(std::ios::skipws);
|
||||
spirit::istream_iterator it(is);
|
||||
spirit::istream_iterator end;
|
||||
return from_iterators(it, end, w);
|
||||
}
|
||||
|
||||
///////////////
|
||||
// ACCESSORS //
|
||||
///////////////
|
||||
|
||||
/***
|
||||
* Lookup strings from the catalog
|
||||
*
|
||||
* When using string literals as the parameters, these versions are safe and
|
||||
* are maximally efficient.
|
||||
* (The returned pointer is either the input pointer, having static storage
|
||||
* duration, or has lifetime as long as the catalog.)
|
||||
*
|
||||
* Chosen to behave in the same manner as corresponding gettext functions.
|
||||
*/
|
||||
const char * gettext(const char * msgid) const {
|
||||
auto it = hashmap_.find(msgid);
|
||||
if (it != hashmap_.end()) {
|
||||
return get(it->second).c_str();
|
||||
} else {
|
||||
return msgid;
|
||||
}
|
||||
}
|
||||
|
||||
const char * ngettext(const char * msgid, const char * msgid_plural, uint plural) const {
|
||||
auto it = hashmap_.find(msgid);
|
||||
if (it != hashmap_.end() && it->second.is_plural()) {
|
||||
return get(it->second, plural).c_str();
|
||||
} else {
|
||||
return (plural == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
}
|
||||
|
||||
const char * pgettext(const char * context, const char * msgid) const {
|
||||
auto it = hashmap_.find(form_context_index(context, msgid));
|
||||
if (it != hashmap_.end()) {
|
||||
return get(it->second).c_str();
|
||||
} else {
|
||||
return msgid;
|
||||
}
|
||||
}
|
||||
|
||||
const char * npgettext(const char * context, const char * msgid, const char * msgid_plural, uint plural) const {
|
||||
auto it = hashmap_.find(form_context_index(context, msgid));
|
||||
if (it != hashmap_.end() && it->second.is_plural()) {
|
||||
return get(it->second, plural).c_str();
|
||||
} else {
|
||||
return (plural == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Lookup strings from catalog, return std::string.
|
||||
*
|
||||
* When, for whatever reason, it is more comfortable to use idiomatic C++.
|
||||
*/
|
||||
std::string gettext_str(const std::string & msgid) const {
|
||||
auto it = hashmap_.find(msgid);
|
||||
if (it != hashmap_.end()) {
|
||||
return get(it->second);
|
||||
} else {
|
||||
return msgid;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ngettext_str(const std::string & msgid, const std::string & msgid_plural, uint plural) const {
|
||||
auto it = hashmap_.find(msgid);
|
||||
if (it != hashmap_.end() && it->second.is_plural()) {
|
||||
return get(it->second, plural);
|
||||
} else {
|
||||
return (plural == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
}
|
||||
|
||||
std::string pgettext_str(const std::string & context, const std::string & msgid) const {
|
||||
auto it = hashmap_.find(form_context_index(context, msgid));
|
||||
if (it != hashmap_.end()) {
|
||||
return get(it->second);
|
||||
} else {
|
||||
return msgid;
|
||||
}
|
||||
}
|
||||
|
||||
std::string npgettext_str(const std::string & context, const std::string & msgid, const std::string & msgid_plural, uint plural) const {
|
||||
auto it = hashmap_.find(form_context_index(context, msgid));
|
||||
if (it != hashmap_.end() && it->second.is_plural()) {
|
||||
return get(it->second, plural);
|
||||
} else {
|
||||
return (plural == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Get line numbers of messages
|
||||
*/
|
||||
std::size_t gettext_line_no(const std::string & msgid) const {
|
||||
auto it = hashmap_.find(msgid);
|
||||
if (it != hashmap_.end()) {
|
||||
return it->second.line_no;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t pgettext_line_no(const std::string & context, const std::string & msgid) const {
|
||||
auto it = hashmap_.find(form_context_index(context, msgid));
|
||||
if (it != hashmap_.end()) {
|
||||
return it->second.line_no;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Access metadata
|
||||
*/
|
||||
const catalog_metadata & get_metadata() const { return metadata_; }
|
||||
|
||||
/***
|
||||
* Catalog size
|
||||
*/
|
||||
uint size() const {
|
||||
// exclude po header from the count, this is how msgfmt reports size also
|
||||
return hashmap_.size() - hashmap_.count("");
|
||||
}
|
||||
|
||||
/***
|
||||
* Debugging output
|
||||
*/
|
||||
const hashmap_type & get_hashmap() const { return hashmap_; }
|
||||
|
||||
/***
|
||||
* Set warning channel (for msgid overwrites)
|
||||
*/
|
||||
void set_warning_channel(const warning_channel_type & w) { warning_channel_ = w; }
|
||||
|
||||
/***
|
||||
* Merge a different catalog into this one
|
||||
*/
|
||||
template <typename H, typename P>
|
||||
void merge(catalog<H, P> && other) {
|
||||
std::string maybe_error = metadata_.check_compatibility(other.metadata_);
|
||||
if (maybe_error.size()) {
|
||||
SPIRIT_PO_CATALOG_FAIL(("Cannot merge catalogs: " + maybe_error));
|
||||
}
|
||||
for (auto & p : other.hashmap_) {
|
||||
if (p.first.size()) { // don't copy over the header, keep our original header
|
||||
insert_message(std::move(p.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_CATALOG_HPP_INCLUDED
|
154
src/spirit_po/catalog_metadata.hpp
Normal file
154
src/spirit_po/catalog_metadata.hpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
|
||||
#define SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
|
||||
|
||||
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#define BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#endif
|
||||
|
||||
#include <spirit_po/exceptions.hpp>
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
|
||||
typedef unsigned int uint;
|
||||
typedef std::pair<uint, std::string> num_plurals_info;
|
||||
|
||||
struct catalog_metadata {
|
||||
std::string project_id;
|
||||
std::string language;
|
||||
std::string language_team;
|
||||
std::string last_translator;
|
||||
|
||||
uint num_plural_forms;
|
||||
std::string plural_forms_function_string;
|
||||
|
||||
std::string charset;
|
||||
|
||||
catalog_metadata()
|
||||
: project_id()
|
||||
, language()
|
||||
, language_team()
|
||||
, last_translator()
|
||||
, num_plural_forms(0)
|
||||
, plural_forms_function_string()
|
||||
{}
|
||||
|
||||
private:
|
||||
std::string find_header_line(const std::string & header, const std::string & label) {
|
||||
size_t idx = header.find(label);
|
||||
if (idx == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
auto it = header.begin() + idx + label.size();
|
||||
while (it != header.end() && *it == ' ') { ++it; }
|
||||
|
||||
auto e = it;
|
||||
while (e != header.end() && *e != '\n') { ++e; }
|
||||
return std::string(it, e);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
struct num_plurals_grammar : qi::grammar<Iterator, num_plurals_info()> {
|
||||
qi::rule<Iterator, num_plurals_info()> main;
|
||||
num_plurals_grammar() : num_plurals_grammar::base_type(main) {
|
||||
using qi::lit;
|
||||
main = qi::skip(' ') [ lit("nplurals=") >> qi::uint_ >> lit(';') >> lit("plural=") ] >> (*qi::char_);
|
||||
}
|
||||
};
|
||||
|
||||
#define DEFAULT_CHARSET "UTF-8"
|
||||
|
||||
template <typename Iterator>
|
||||
struct content_type_grammar : qi::grammar<Iterator, std::string()> {
|
||||
qi::rule<Iterator, std::string()> main;
|
||||
content_type_grammar() : content_type_grammar::base_type(main) {
|
||||
using qi::lit;
|
||||
using qi::omit;
|
||||
using qi::skip;
|
||||
main = skip(' ')[ omit[ *(qi::char_ - ';') >> lit(';') ] >> ((lit("charset=") >> *(qi::char_)) | qi::attr(DEFAULT_CHARSET)) ];
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
// nonempty return is an error mesage
|
||||
std::string parse_header(const std::string & header) {
|
||||
constexpr const char * default_charset = DEFAULT_CHARSET;
|
||||
#undef DEFAULT_CHARSET
|
||||
|
||||
project_id = find_header_line(header, "Project-Id-Version:");
|
||||
language = find_header_line(header, "Language:");
|
||||
language_team = find_header_line(header, "Language-Team:");
|
||||
last_translator = find_header_line(header, "Last-Translator:");
|
||||
|
||||
std::string content_type_line = find_header_line(header, "Content-Type:");
|
||||
if (content_type_line.size()) {
|
||||
auto it = content_type_line.begin();
|
||||
auto end = content_type_line.end();
|
||||
content_type_grammar<decltype(it)> gram;
|
||||
std::string ct;
|
||||
if (qi::parse(it, end, gram, ct)) {
|
||||
charset = ct;
|
||||
if (charset != "ASCII" && charset != "UTF-8") {
|
||||
return "PO file declared charset of '" + charset + "', but spirit_po only supports UTF-8 and ASCII for this.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assume defaults for charset
|
||||
charset = default_charset;
|
||||
}
|
||||
|
||||
std::string content_transfer_encoding = find_header_line(header, "Content-Transfer-Encoding:");
|
||||
if (content_transfer_encoding.size()) {
|
||||
auto it = content_transfer_encoding.begin();
|
||||
auto end = content_transfer_encoding.end();
|
||||
if (!qi::phrase_parse(it, end, qi::lit("8bit"), qi::ascii::space)) {
|
||||
return "PO header 'Content-Transfer-Encoding' must be '8bit' if specified, but PO file declared '" + content_transfer_encoding + "'";
|
||||
}
|
||||
}
|
||||
|
||||
std::string num_plurals_line = find_header_line(header, "Plural-Forms:");
|
||||
|
||||
if (num_plurals_line.size()) {
|
||||
auto it = num_plurals_line.begin();
|
||||
auto end = num_plurals_line.end();
|
||||
|
||||
num_plurals_grammar<decltype(it)> gram;
|
||||
num_plurals_info info;
|
||||
if (qi::parse(it, end, gram, info)) {
|
||||
num_plural_forms = info.first;
|
||||
plural_forms_function_string = info.second;
|
||||
} else {
|
||||
num_plural_forms = 0;
|
||||
plural_forms_function_string = "";
|
||||
return "Failed to parse Plural-Forms entry -- stopped at:\n" + string_iterator_context(num_plurals_line, it);
|
||||
}
|
||||
} else {
|
||||
num_plural_forms = 2;
|
||||
plural_forms_function_string = "n != 1";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// check if this metadata is compatible with another metadata (number of plural forms, maybe other criteria)
|
||||
// return a nonempty string containing error message if they are not compatible.
|
||||
std::string check_compatibility(const catalog_metadata & other) const {
|
||||
if (num_plural_forms != other.num_plural_forms) {
|
||||
return std::string{"Num plural forms mismatch. this = "} + std::to_string(num_plural_forms) + " other = " + std::to_string(other.num_plural_forms);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_CATALOG_METADATA_HPP_INCLUDED
|
120
src/spirit_po/default_plural_forms_compiler.hpp
Normal file
120
src/spirit_po/default_plural_forms_compiler.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_DEFAULT_PLURAL_FORMS_COMPILER_HPP_INCLUDED
|
||||
#define SPIRIT_PO_DEFAULT_PLURAL_FORMS_COMPILER_HPP_INCLUDED
|
||||
|
||||
/***
|
||||
* In GNU gettext, a language is permitted to define any number of 'plural forms'.
|
||||
* For instance, in English and most romance languages there are only two forms,
|
||||
* singular and plural. However in many other languages, there may be only one
|
||||
* form, or there may be many plural forms reserved for various numbers of items.
|
||||
*
|
||||
* In the header of a po file, as part of the metadata, translators are expected
|
||||
* to specify exactly how many plural forms there are, (how many different
|
||||
* variations of a pluralized string they will provide), and also a function that
|
||||
* computes which form (the appropriate index) should be used when the number of
|
||||
* items is a number "n".
|
||||
*
|
||||
* Traditionally, this function is specified as a single line of pseudo C code.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Russian:
|
||||
* Po header:
|
||||
* num_plurals = 3
|
||||
* plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
|
||||
* Messages:
|
||||
*
|
||||
*
|
||||
* There are many many more examples shown here:
|
||||
* http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
|
||||
*
|
||||
* The code in *this* file is concerned with converting these strings into
|
||||
* function objects implementing a function uint -> uint.
|
||||
*
|
||||
* These function objects are then associated to each catalog and used when
|
||||
* looking up plurals.
|
||||
*
|
||||
* In spirit-po, we provide support for the standard gettext pseudo-C language
|
||||
* using the 'default_plural_forms_compiler', which compiles these run-time
|
||||
* pseudo-C expressions into expression trees which can be evaluated.
|
||||
*
|
||||
* By using non-default template parameters and providing an appropriate
|
||||
* function object, you can make spirit-po use your favorite programming
|
||||
* language for these instead. (Or, your translators' favorite?)
|
||||
*
|
||||
* The 'plural_forms_compiler' concept must be a class/struct and provide:
|
||||
* - The plural_forms_compiler must be default constructible.
|
||||
* - It must have a typedef 'result_type' which is the type of the function
|
||||
* object it produces.
|
||||
* - An operator() overload which takes const std::string &, and return an
|
||||
* instance of 'result_type'.
|
||||
* - result_type must be default constructible and move constructible.
|
||||
* - result_type must have an operator() overload which takes and yields
|
||||
* unsigned int.
|
||||
* - result_type must have an explicit operator bool() const overload which
|
||||
* returns whether the function object is valid (compilation succeeded)
|
||||
* - result_type must have a function `error()` which returns a std::string
|
||||
* representing a compilation error message in the case of failure.
|
||||
*/
|
||||
|
||||
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#define BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#endif
|
||||
|
||||
#include <spirit_po/default_plural_forms_expressions.hpp>
|
||||
#include <spirit_po/exceptions.hpp>
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
typedef unsigned int uint;
|
||||
|
||||
namespace default_plural_forms {
|
||||
|
||||
class function_object {
|
||||
mutable stack_machine machine_;
|
||||
boost::optional<std::string> parse_error_;
|
||||
|
||||
public:
|
||||
function_object(const expr & _e) : machine_(_e), parse_error_() {}
|
||||
function_object(const std::string & s) : machine_(n_var()), parse_error_(s) {}
|
||||
function_object() : function_object(std::string{"uninitialized"}) {}
|
||||
|
||||
uint operator()(uint n) const {
|
||||
return machine_.compute(n);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return !parse_error_; }
|
||||
std::string error() const { return *parse_error_; }
|
||||
};
|
||||
|
||||
struct compiler {
|
||||
typedef function_object result_type;
|
||||
result_type operator()(const std::string & str) const {
|
||||
expr e;
|
||||
|
||||
typedef std::string::const_iterator str_it;
|
||||
str_it it = str.begin();
|
||||
str_it end = str.end();
|
||||
op_grammar<str_it> grammar;
|
||||
|
||||
if (qi::phrase_parse(it, end, grammar, qi::space, e) && it == end) {
|
||||
return function_object(std::move(e));
|
||||
} else {
|
||||
return function_object("Plural-Forms expression reader: Could not parse expression, stopped parsing at:\n" + string_iterator_context(str, it));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace default_plura_forms
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_DEFAULT_PLURAL_FORMS_COMPILER_HPP_INCLUDED
|
577
src/spirit_po/default_plural_forms_expressions.hpp
Normal file
577
src/spirit_po/default_plural_forms_expressions.hpp
Normal file
|
@ -0,0 +1,577 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
|
||||
#define SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
|
||||
|
||||
/***
|
||||
* The namespace default_plural_forms contains all the details to implement
|
||||
* the subset of the C grammar used by standard GNU gettext po headers.
|
||||
*
|
||||
* Boolean expressions return uint 0 or 1.
|
||||
*
|
||||
* The 'compiler' is a spirit grammar which parses a string into an expression
|
||||
* object. The expressions are evaluated by a simple stack machine.
|
||||
*/
|
||||
|
||||
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#define BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/include/phoenix_core.hpp>
|
||||
#include <boost/spirit/include/phoenix_operator.hpp>
|
||||
#include <boost/fusion/include/adapt_struct.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
#include <boost/variant/recursive_wrapper.hpp>
|
||||
|
||||
#ifdef SPIRIT_PO_DEBUG
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
typedef unsigned int uint;
|
||||
|
||||
namespace default_plural_forms {
|
||||
|
||||
// X Macro for repetitive binary ops declarations
|
||||
|
||||
#define FOREACH_SPIRIT_PO_BINARY_OP(X_) \
|
||||
X_(eq_op, ==) X_(neq_op, !=) X_(ge_op, >=) X_(le_op, <=) X_(gt_op, >) X_(lt_op, <) X_(mod_op, %)
|
||||
|
||||
// && and || are treated slightly differently from other binary ops
|
||||
|
||||
#define FOREACH_SPIRIT_PO_CONJUNCTION(X_) \
|
||||
X_(and_op, &&) X_(or_op, ||)
|
||||
|
||||
/***
|
||||
* Declare / forward declare expr struct types
|
||||
*/
|
||||
|
||||
struct constant { uint value; };
|
||||
struct n_var { n_var() = default; explicit n_var(char) {}}; // work around a quirk in spirit
|
||||
struct not_op;
|
||||
struct ternary_op;
|
||||
|
||||
#define FWD_DECL_(name, op) \
|
||||
struct name ; \
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(FWD_DECL_)
|
||||
FOREACH_SPIRIT_PO_CONJUNCTION(FWD_DECL_)
|
||||
|
||||
#undef FWD_DECL_
|
||||
|
||||
/***
|
||||
* Define expr variant type
|
||||
*/
|
||||
|
||||
#define WRAP_(name, op) boost::recursive_wrapper< name >, \
|
||||
|
||||
typedef boost::variant<constant, n_var, boost::recursive_wrapper<not_op>,
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(WRAP_)
|
||||
FOREACH_SPIRIT_PO_CONJUNCTION(WRAP_)
|
||||
boost::recursive_wrapper<ternary_op>> expr;
|
||||
|
||||
#undef WRAP_
|
||||
|
||||
/***
|
||||
* Define structs
|
||||
*/
|
||||
|
||||
struct not_op { expr e1; };
|
||||
struct ternary_op { expr e1, e2, e3; };
|
||||
|
||||
#define DECL_(name, op) \
|
||||
struct name { expr e1, e2; }; \
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(DECL_)
|
||||
FOREACH_SPIRIT_PO_CONJUNCTION(DECL_)
|
||||
|
||||
#undef DECL_
|
||||
|
||||
/***
|
||||
* Visitor that naively evaluates expressions
|
||||
*/
|
||||
struct evaluator : public boost::static_visitor<uint> {
|
||||
uint n_value_;
|
||||
explicit evaluator(uint n) : n_value_(n) {}
|
||||
|
||||
uint operator()(const constant & c) const { return c.value; }
|
||||
uint operator()(n_var) const { return n_value_; }
|
||||
uint operator()(const not_op & op) const { return !boost::apply_visitor(*this, op.e1); }
|
||||
|
||||
#define EVAL_OP_(name, OPERATOR) \
|
||||
uint operator()(const name & op) const { return (boost::apply_visitor(*this, op.e1)) OPERATOR (boost::apply_visitor(*this, op.e2)); } \
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(EVAL_OP_)
|
||||
FOREACH_SPIRIT_PO_CONJUNCTION(EVAL_OP_)
|
||||
#undef EVAL_OP_
|
||||
|
||||
uint operator()(const ternary_op & op) const { return boost::apply_visitor(*this, op.e1) ? boost::apply_visitor(*this, op.e2) : boost::apply_visitor(*this, op.e3); }
|
||||
};
|
||||
|
||||
} // end namespace default_plural_forms
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
/***
|
||||
* Adapt structs for fusion / qi
|
||||
*/
|
||||
|
||||
BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::constant,
|
||||
(uint, value))
|
||||
BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::not_op,
|
||||
(spirit_po::default_plural_forms::expr, e1))
|
||||
BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms::ternary_op,
|
||||
(spirit_po::default_plural_forms::expr, e1)
|
||||
(spirit_po::default_plural_forms::expr, e2)
|
||||
(spirit_po::default_plural_forms::expr, e3))
|
||||
|
||||
#define ADAPT_STRUCT_(name, op) \
|
||||
BOOST_FUSION_ADAPT_STRUCT(spirit_po::default_plural_forms:: name, \
|
||||
(spirit_po::default_plural_forms::expr, e1) \
|
||||
(spirit_po::default_plural_forms::expr, e2)) \
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(ADAPT_STRUCT_)
|
||||
FOREACH_SPIRIT_PO_CONJUNCTION(ADAPT_STRUCT_)
|
||||
|
||||
#undef ADAPT_STRUCT_
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
namespace default_plural_forms {
|
||||
|
||||
/***
|
||||
* Pseudo-C Grammar
|
||||
*
|
||||
* Note that the grammar has been somewhat optimized by using local variables
|
||||
* and inherited attributes, in order to avoid exponential backtracking overhead.
|
||||
* This makes it a little harder to read than if we got rid of all local variables,
|
||||
* but then it is too slow to parse the expressions for certain languages.
|
||||
*
|
||||
* The main idea is that instead of parsing things like
|
||||
*
|
||||
* BINARY_OP = LOWER_PRECENDENCE >> BINARY_OP_LITERAL >> CURRENT_PRECEDENCE
|
||||
* CURRENT_PRECEDENCE = BINARY_OP | OTHER_OP | YET_ANOTHER_OP | LOWER_PRECEDENCE
|
||||
*
|
||||
* (which is bad because if the binary op literal is not there then we have to
|
||||
* backtrack through an entire subexpression)
|
||||
*
|
||||
* we make BINARY_OP take the subexpression as a parameter, and in each
|
||||
* precedence level, we capture the subexpression first and store it in a local
|
||||
* variable, so that it does not get reparsed when we backtrack.
|
||||
*
|
||||
* BINARY_OP = BINARY_OP_LITERAL >> qi::attr(parameter) >> CURRENT_PRECEDENCE
|
||||
*
|
||||
* CURRENT_PRECEDENCE = LOWER_PRECEDENCE[local_var = result] >>
|
||||
* (BINARY_OP(local_var) | OTHER_OP(local_var) | YET_ANOTHER_OP(local_var) | qi::attr(local_var)
|
||||
*
|
||||
*/
|
||||
|
||||
template <typename Iterator>
|
||||
struct op_grammar : qi::grammar<Iterator, expr(), qi::space_type> {
|
||||
qi::rule<Iterator, constant(), qi::space_type> constant_;
|
||||
qi::rule<Iterator, n_var(), qi::space_type> n_;
|
||||
qi::rule<Iterator, not_op(), qi::space_type> not_;
|
||||
qi::rule<Iterator, and_op(expr), qi::space_type> and_;
|
||||
qi::rule<Iterator, or_op(expr), qi::space_type> or_;
|
||||
qi::rule<Iterator, eq_op(expr), qi::space_type> eq_;
|
||||
qi::rule<Iterator, neq_op(expr), qi::space_type> neq_;
|
||||
qi::rule<Iterator, ge_op(expr), qi::space_type> ge_;
|
||||
qi::rule<Iterator, le_op(expr), qi::space_type> le_;
|
||||
qi::rule<Iterator, gt_op(expr), qi::space_type> gt_;
|
||||
qi::rule<Iterator, lt_op(expr), qi::space_type> lt_;
|
||||
qi::rule<Iterator, mod_op(expr), qi::space_type> mod_;
|
||||
qi::rule<Iterator, ternary_op(expr), qi::space_type> ternary_;
|
||||
qi::rule<Iterator, expr(), qi::space_type> paren_expr_;
|
||||
|
||||
// expression precedence levels
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> ternary_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> or_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> and_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> eq_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> rel_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type, qi::locals<expr>> mod_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type> atom_level_;
|
||||
qi::rule<Iterator, expr(), qi::space_type> expr_;
|
||||
|
||||
// handle optional ';' at end
|
||||
qi::rule<Iterator, expr(), qi::space_type> main_;
|
||||
|
||||
op_grammar() : op_grammar::base_type(main_) {
|
||||
using qi::attr;
|
||||
using qi::lit;
|
||||
|
||||
constant_ = qi::uint_;
|
||||
n_ = qi::char_('n');
|
||||
paren_expr_ = lit('(') >> expr_ >> lit(')');
|
||||
not_ = lit('!') >> atom_level_;
|
||||
atom_level_ = paren_expr_ | not_ | n_ | constant_;
|
||||
|
||||
mod_ = lit('%') >> attr(qi::_r1) >> atom_level_;
|
||||
mod_level_ = qi::omit[atom_level_[qi::_a = qi::_1]] >> (mod_(qi::_a) | attr(qi::_a));
|
||||
|
||||
ge_ = lit(">=") >> attr(qi::_r1) >> mod_level_;
|
||||
le_ = lit("<=") >> attr(qi::_r1) >> mod_level_;
|
||||
gt_ = lit('>') >> attr(qi::_r1) >> mod_level_;
|
||||
lt_ = lit('<') >> attr(qi::_r1) >> mod_level_;
|
||||
rel_level_ = qi::omit[mod_level_[qi::_a = qi::_1]] >> (ge_(qi::_a) | le_(qi::_a) | gt_(qi::_a) | lt_(qi::_a) | attr(qi::_a));
|
||||
|
||||
eq_ = lit("==") >> attr(qi::_r1) >> rel_level_;
|
||||
neq_ = lit("!=") >> attr(qi::_r1) >> rel_level_;
|
||||
eq_level_ = qi::omit[rel_level_[qi::_a = qi::_1]] >> (eq_(qi::_a) | neq_(qi::_a) | attr(qi::_a));
|
||||
|
||||
and_ = lit("&&") >> attr(qi::_r1) >> and_level_;
|
||||
and_level_ = qi::omit[eq_level_[qi::_a = qi::_1]] >> (and_(qi::_a) | attr(qi::_a));
|
||||
|
||||
or_ = lit("||") >> attr(qi::_r1) >> or_level_;
|
||||
or_level_ = qi::omit[and_level_[qi::_a = qi::_1]] >> (or_(qi::_a) | attr(qi::_a));
|
||||
|
||||
ternary_ = lit('?') >> attr(qi::_r1) >> ternary_level_ >> lit(':') >> ternary_level_;
|
||||
ternary_level_ = qi::omit[or_level_[qi::_a = qi::_1]] >> (ternary_(qi::_a) | attr(qi::_a));
|
||||
|
||||
expr_ = ternary_level_;
|
||||
|
||||
main_ = expr_ >> -lit(';');
|
||||
}
|
||||
};
|
||||
|
||||
/***
|
||||
* Now define a simple stack machine to evaluate the expressions efficiently.
|
||||
*
|
||||
* First define op_codes
|
||||
*/
|
||||
|
||||
#define ENUMERATE(X, Y) X,
|
||||
|
||||
enum class op_code { n_var, FOREACH_SPIRIT_PO_BINARY_OP(ENUMERATE) not_op };
|
||||
|
||||
#undef ENUMERATE
|
||||
|
||||
/// Instruction that causes us to skip upcoming instructions
|
||||
struct skip {
|
||||
uint distance;
|
||||
};
|
||||
|
||||
/// Instructions that conditionally cause us to skip upcoming instructions
|
||||
struct skip_if {
|
||||
uint distance;
|
||||
};
|
||||
|
||||
struct skip_if_not {
|
||||
uint distance;
|
||||
};
|
||||
|
||||
/***
|
||||
* Instruction is a variant type that represents either a push_constant, branch, jump, or arithmetic op.
|
||||
*/
|
||||
typedef boost::variant<constant, skip, skip_if, skip_if_not, op_code> instruction;
|
||||
|
||||
/***
|
||||
* Debug strings for instruction set
|
||||
*/
|
||||
#ifdef SPIRIT_PO_DEBUG
|
||||
inline std::string op_code_string(op_code oc) {
|
||||
std::string result = "[ ";
|
||||
switch (oc) {
|
||||
case op_code::n_var: {
|
||||
result += "n ";
|
||||
break;
|
||||
}
|
||||
case op_code::not_op: {
|
||||
result += "! ";
|
||||
break;
|
||||
}
|
||||
#define OP_CODE_STR_CASE_(X, Y) \
|
||||
case op_code::X: { \
|
||||
result += #Y; \
|
||||
break; \
|
||||
}
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(OP_CODE_STR_CASE_)
|
||||
|
||||
#undef OP_CODE_STR_CASE_
|
||||
}
|
||||
|
||||
if (result.size() < 5) { result += ' '; } \
|
||||
result += " : ]";
|
||||
return result;
|
||||
}
|
||||
|
||||
struct instruction_debug_string_maker : boost::static_visitor<std::string> {
|
||||
std::string operator()(const constant & c) const {
|
||||
return "[ push : " + std::to_string(c.value) + " ]";
|
||||
}
|
||||
std::string operator()(const skip & s) const {
|
||||
return "[ skip : " + std::to_string(s.distance) + " ]";
|
||||
}
|
||||
std::string operator()(const skip_if & s) const {
|
||||
return "[ sif : " + std::to_string(s.distance) + " ]";
|
||||
}
|
||||
std::string operator()(const skip_if_not & s) const {
|
||||
return "[ sifn : " + std::to_string(s.distance) + " ]";
|
||||
}
|
||||
std::string operator()(const op_code & oc) const {
|
||||
return op_code_string(oc);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::string debug_string(const instruction & i) {
|
||||
return boost::apply_visitor(instruction_debug_string_maker{}, i);
|
||||
}
|
||||
|
||||
#endif // SPIRIT_PO_DEBUG
|
||||
|
||||
/***
|
||||
* Helper: Check if an expression obviously is zero-one valued
|
||||
*/
|
||||
struct is_boolean : public boost::static_visitor<bool> {
|
||||
bool operator()(const and_op &) const { return true; }
|
||||
bool operator()(const or_op &) const { return true; }
|
||||
bool operator()(const not_op &) const { return true; }
|
||||
bool operator()(const eq_op &) const { return true; }
|
||||
bool operator()(const neq_op &) const { return true; }
|
||||
bool operator()(const ge_op &) const { return true; }
|
||||
bool operator()(const le_op &) const { return true; }
|
||||
bool operator()(const gt_op &) const { return true; }
|
||||
bool operator()(const lt_op &) const { return true; }
|
||||
bool operator()(const n_var &) const { return false; }
|
||||
bool operator()(const constant & c) const { return (c.value == 0 || c.value == 1); }
|
||||
bool operator()(const mod_op & m) const { return boost::apply_visitor(*this, m.e1); }
|
||||
bool operator()(const ternary_op & t) const { return boost::apply_visitor(*this, t.e2) && boost::apply_visitor(*this, t.e3); }
|
||||
};
|
||||
|
||||
|
||||
/***
|
||||
* Visitor that maps expressions to instruction sequences
|
||||
*/
|
||||
struct emitter : public boost::static_visitor<std::vector<instruction>> {
|
||||
std::vector<instruction> operator()(const constant & c) const {
|
||||
return std::vector<instruction>{instruction{c}};
|
||||
}
|
||||
std::vector<instruction> operator()(const n_var &) const {
|
||||
return std::vector<instruction>{instruction{op_code::n_var}};
|
||||
}
|
||||
std::vector<instruction> operator()(const not_op & o) const {
|
||||
auto result = boost::apply_visitor(*this, o.e1);
|
||||
result.emplace_back(op_code::not_op);
|
||||
return result;
|
||||
}
|
||||
#define EMIT_OP_(name, op) \
|
||||
std::vector<instruction> operator()(const name & o) const { \
|
||||
auto result = boost::apply_visitor(*this, o.e1); \
|
||||
auto temp = boost::apply_visitor(*this, o.e2); \
|
||||
std::move(temp.begin(), temp.end(), std::back_inserter(result)); \
|
||||
result.emplace_back(op_code::name); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(EMIT_OP_)
|
||||
|
||||
#undef EMIT_OP_
|
||||
|
||||
/***
|
||||
* We make &&, ||, and ? shortcut
|
||||
*/
|
||||
std::vector<instruction> operator()(const and_op & o) const {
|
||||
auto result = boost::apply_visitor(*this, o.e1);
|
||||
auto second = boost::apply_visitor(*this, o.e2);
|
||||
bool second_is_boolean = boost::apply_visitor(is_boolean{}, o.e2);
|
||||
|
||||
uint sec_size = static_cast<uint>(second.size());
|
||||
if (!second_is_boolean) { sec_size += 2; }
|
||||
|
||||
result.emplace_back(skip_if{2});
|
||||
result.emplace_back(constant{0});
|
||||
result.emplace_back(skip{sec_size});
|
||||
|
||||
std::move(second.begin(), second.end(), std::back_inserter(result));
|
||||
if (!second_is_boolean) {
|
||||
result.emplace_back(op_code::not_op);
|
||||
result.emplace_back(op_code::not_op);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<instruction> operator()(const or_op & o) const {
|
||||
auto result = boost::apply_visitor(*this, o.e1);
|
||||
auto second = boost::apply_visitor(*this, o.e2);
|
||||
bool second_is_boolean = boost::apply_visitor(is_boolean{}, o.e2);
|
||||
|
||||
uint sec_size = static_cast<uint>(second.size());
|
||||
if (!second_is_boolean) { sec_size += 2; }
|
||||
|
||||
result.emplace_back(skip_if_not{2});
|
||||
result.emplace_back(constant{1});
|
||||
result.emplace_back(skip{sec_size});
|
||||
|
||||
std::move(second.begin(), second.end(), std::back_inserter(result));
|
||||
if (!second_is_boolean) {
|
||||
result.emplace_back(op_code::not_op);
|
||||
result.emplace_back(op_code::not_op);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<instruction> operator()(const ternary_op & o) const {
|
||||
auto result = boost::apply_visitor(*this, o.e1);
|
||||
auto tbranch = boost::apply_visitor(*this, o.e2);
|
||||
auto fbranch = boost::apply_visitor(*this, o.e3);
|
||||
|
||||
uint tsize = static_cast<uint>(tbranch.size());
|
||||
uint fsize = static_cast<uint>(fbranch.size());
|
||||
|
||||
// We use jump if / jump if not in the way that will let us put the shorter branch first.
|
||||
if (tbranch.size() > fbranch.size()) {
|
||||
// + 1 to size because we have to put a jump at end of this branch also
|
||||
result.emplace_back(skip_if{fsize + 1});
|
||||
std::move(fbranch.begin(), fbranch.end(), std::back_inserter(result));
|
||||
result.emplace_back(skip{tsize});
|
||||
std::move(tbranch.begin(), tbranch.end(), std::back_inserter(result));
|
||||
} else {
|
||||
result.emplace_back(skip_if_not{tsize + 1});
|
||||
std::move(tbranch.begin(), tbranch.end(), std::back_inserter(result));
|
||||
result.emplace_back(skip{fsize});
|
||||
std::move(fbranch.begin(), fbranch.end(), std::back_inserter(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/***
|
||||
* Actual stack machine
|
||||
*/
|
||||
|
||||
class stack_machine : public boost::static_visitor<uint> {
|
||||
std::vector<instruction> instruction_seq_;
|
||||
std::vector<uint> stack_;
|
||||
uint n_value_;
|
||||
|
||||
#ifdef SPIRIT_PO_DEBUG
|
||||
public:
|
||||
void debug_print_instructions() const {
|
||||
std::cerr << "Instruction sequence:\n";
|
||||
for (const auto & i : instruction_seq_) {
|
||||
std::cerr << debug_string(i) << std::endl;
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
#define MACHINE_ASSERT(X) \
|
||||
do { \
|
||||
if (!(X)) { \
|
||||
std::cerr << "Stack machine failure:\n"; \
|
||||
debug_print_instructions(); \
|
||||
assert(false && #X); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else // SPIRIT_PO_DEBUG
|
||||
|
||||
#define MACHINE_ASSERT(...) do {} while(0)
|
||||
|
||||
#endif // SPIRIT_PO_DEBUG
|
||||
|
||||
uint pop_one() {
|
||||
MACHINE_ASSERT(stack_.size());
|
||||
|
||||
uint result = stack_.back();
|
||||
stack_.resize(stack_.size() - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit stack_machine(const expr & e)
|
||||
: instruction_seq_(boost::apply_visitor(emitter{}, e))
|
||||
, stack_()
|
||||
, n_value_()
|
||||
{}
|
||||
|
||||
/***
|
||||
* operator() takes the instruction that we should execute
|
||||
* It should perform the operation adjusting the stack
|
||||
* It returns the amount by which we should increment the
|
||||
* program counter.
|
||||
*/
|
||||
uint operator()(const constant & c) {
|
||||
stack_.emplace_back(c.value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint operator()(const skip & s) {
|
||||
return 1 + s.distance;
|
||||
}
|
||||
|
||||
uint operator()(const skip_if & s) {
|
||||
return 1 + (pop_one() ? s.distance : 0);
|
||||
}
|
||||
|
||||
uint operator()(const skip_if_not & s) {
|
||||
return 1 + (pop_one() ? 0 : s.distance);
|
||||
}
|
||||
|
||||
uint operator()(op_code oc) {
|
||||
switch (oc) {
|
||||
case op_code::n_var: {
|
||||
stack_.emplace_back(n_value_);
|
||||
return 1;
|
||||
}
|
||||
case op_code::not_op: {
|
||||
MACHINE_ASSERT(stack_.size());
|
||||
stack_.back() = !stack_.back();
|
||||
return 1;
|
||||
}
|
||||
#define STACK_MACHINE_CASE_(name, op) \
|
||||
case op_code::name: { \
|
||||
MACHINE_ASSERT(stack_.size() >= 2); \
|
||||
uint parm2 = pop_one(); \
|
||||
\
|
||||
if (op_code::name == op_code::mod_op) { \
|
||||
MACHINE_ASSERT(parm2 && "Division by zero when evaluating gettext plural form expression"); \
|
||||
} \
|
||||
\
|
||||
stack_.back() = (stack_.back() op parm2); \
|
||||
return 1; \
|
||||
}
|
||||
|
||||
FOREACH_SPIRIT_PO_BINARY_OP(STACK_MACHINE_CASE_)
|
||||
|
||||
#undef STACK_MACHINE_CASE_
|
||||
}
|
||||
MACHINE_ASSERT(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint compute(uint arg) {
|
||||
n_value_ = arg;
|
||||
stack_.resize(0);
|
||||
uint pc = 0;
|
||||
while (pc < instruction_seq_.size()) {
|
||||
pc += boost::apply_visitor(*this, instruction_seq_[pc]);
|
||||
}
|
||||
MACHINE_ASSERT(pc == instruction_seq_.size());
|
||||
MACHINE_ASSERT(stack_.size() == 1);
|
||||
return stack_[0];
|
||||
}
|
||||
};
|
||||
|
||||
#undef MACHINE_ASSERT
|
||||
|
||||
// X macros not used anymore
|
||||
#undef FOREACH_SPIRIT_PO_BINARY_OP
|
||||
#undef FOREACH_SPIRIT_PO_CONJUNCTION
|
||||
|
||||
} // end namespace default_plural_forms
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_DEFAULT_PLURAL_FORMS_EXPRESSIONS_HPP_INCLUDED
|
79
src/spirit_po/exceptions.hpp
Normal file
79
src/spirit_po/exceptions.hpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_EXCEPTIONS_HPP_INCLUDED
|
||||
#define SPIRIT_PO_EXCEPTIONS_HPP_INCLUDED
|
||||
|
||||
#include <boost/spirit/include/support_line_pos_iterator.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
// Show the next 80 characters from some iterator position.
|
||||
// Intended to be used for parser error messages
|
||||
template <typename Iterator>
|
||||
std::string iterator_context(Iterator & it, Iterator & end) {
|
||||
std::string result;
|
||||
std::size_t line_no = boost::spirit::get_line(it);
|
||||
if (line_no != static_cast<std::size_t>(-1)) {
|
||||
result = "Line " + std::to_string(line_no) + ":\n";
|
||||
}
|
||||
|
||||
uint count = 80;
|
||||
while (it != end && count) {
|
||||
result += *it;
|
||||
++it;
|
||||
--count;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// When the thing being parsed is a short string, we can give
|
||||
// a better context report
|
||||
inline std::string string_iterator_context(const std::string & str,
|
||||
std::string::const_iterator it) {
|
||||
std::string result{str};
|
||||
result += "\n";
|
||||
|
||||
for (auto temp = str.begin(); temp != it; ++temp) {
|
||||
result += ' ';
|
||||
}
|
||||
result += "^\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
|
||||
#ifdef SPIRIT_PO_NOEXCEPT
|
||||
|
||||
#define SPIRIT_PO_CATALOG_FAIL(Message) \
|
||||
do { \
|
||||
error_message_ = (Message); \
|
||||
return ; \
|
||||
} while(0)
|
||||
|
||||
#else // SPIRIT_PO_NOEXCEPT
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
struct catalog_exception : std::runtime_error {
|
||||
catalog_exception(const char * what) : runtime_error(what) {}
|
||||
catalog_exception(const std::string & what) : runtime_error(what) {}
|
||||
};
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#define SPIRIT_PO_CATALOG_FAIL(Message) \
|
||||
do { \
|
||||
throw spirit_po::catalog_exception(( Message )); \
|
||||
} while(0)
|
||||
|
||||
|
||||
#endif // SPIRIT_PO_NOEXCEPT
|
||||
|
||||
#endif // SPIRIT_PO_EXCEPTIONS_HPP_INCLUDED
|
128
src/spirit_po/po_grammar.hpp
Normal file
128
src/spirit_po/po_grammar.hpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_PO_GRAMMAR_HPP_INCLUDED
|
||||
#define SPIRIT_PO_PO_GRAMMAR_HPP_INCLUDED
|
||||
|
||||
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#define BOOST_SPIRIT_USE_PHOENIX_V3
|
||||
#endif
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/include/phoenix_core.hpp>
|
||||
#include <boost/spirit/include/phoenix_operator.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
#include <boost/fusion/include/define_struct.hpp>
|
||||
|
||||
#include <spirit_po/po_message_adapted.hpp>
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
typedef unsigned int uint;
|
||||
namespace qi = boost::spirit::qi;
|
||||
namespace phoenix = boost::phoenix;
|
||||
|
||||
template <typename Iterator>
|
||||
struct po_grammar : qi::grammar<Iterator, po_message()> {
|
||||
qi::rule<Iterator> white_line;
|
||||
qi::rule<Iterator> comment_line;
|
||||
qi::rule<Iterator> skipped_line;
|
||||
qi::rule<Iterator> skipped_block;
|
||||
|
||||
qi::rule<Iterator, char()> escaped_character;
|
||||
qi::rule<Iterator, std::string()> single_line_string;
|
||||
qi::rule<Iterator, std::string()> multiline_string;
|
||||
|
||||
qi::rule<Iterator, std::string()> message_id;
|
||||
qi::rule<Iterator, std::string()> message_id_plural;
|
||||
qi::rule<Iterator, std::string()> message_context;
|
||||
qi::rule<Iterator, std::string()> message_str;
|
||||
qi::rule<Iterator, std::string(uint)> message_str_plural;
|
||||
|
||||
qi::rule<Iterator, std::vector<std::string>()> message_single_str;
|
||||
qi::rule<Iterator, std::vector<std::string>(uint)> message_strs;
|
||||
|
||||
qi::rule<Iterator, plural_and_strings_type()> message_singular;
|
||||
qi::rule<Iterator, plural_and_strings_type()> message_plural;
|
||||
|
||||
qi::rule<Iterator, po_message()> message;
|
||||
|
||||
// Related to parsing "fuzzy" po comment
|
||||
qi::rule<Iterator, qi::locals<bool>> fuzzy;
|
||||
qi::rule<Iterator> preamble_comment_line;
|
||||
qi::rule<Iterator> preamble_comment_block;
|
||||
|
||||
/// consume any number of blocks, consisting of any number of comments followed by a white line
|
||||
qi::rule<Iterator> ignored_comments;
|
||||
/// consume any number of non-white comment line (using #). bool result represents if we saw #, fuzzy comment
|
||||
qi::rule<Iterator, bool()> message_preamble;
|
||||
|
||||
po_grammar() : po_grammar::base_type(message) {
|
||||
using qi::attr;
|
||||
using qi::char_;
|
||||
using qi::eoi;
|
||||
using qi::lit;
|
||||
using qi::omit;
|
||||
using qi::uint_;
|
||||
|
||||
white_line = *char_(" \t\r"); // nullable
|
||||
comment_line = char_('#') >> *(char_ - '\n'); // not nullable
|
||||
skipped_line = (comment_line | white_line) >> lit('\n'); // not nullable
|
||||
skipped_block = *skipped_line; // nullable
|
||||
|
||||
// TODO: Do we need to handle other escaped characters?
|
||||
escaped_character = lit('\\') >> (char_("\'\"\\") | (lit('n') >> attr('\n')) | (lit('t') >> attr('\t')));
|
||||
single_line_string = lit('"') >> *(escaped_character | (char_ - '\\' - '"')) >> lit('"');
|
||||
multiline_string = single_line_string % skipped_block; // ^ this is important, if we don't have this then \\ does not have to be escaped in po string, just form an illegal escape code
|
||||
|
||||
message_context = skipped_block >> lit("msgctxt ") >> multiline_string;
|
||||
message_id = skipped_block >> lit("msgid ") >> multiline_string;
|
||||
message_str = skipped_block >> lit("msgstr ") >> multiline_string;
|
||||
message_id_plural = skipped_block >> lit("msgid_plural ") >> multiline_string;
|
||||
message_str_plural = skipped_block >> lit("msgstr[") >> omit[ uint_(qi::_r1) ] >> lit("] ") >> multiline_string;
|
||||
// ^ the index in the po file must match what we expect
|
||||
|
||||
// qi::repeat converts it from a std::string, to a singleton vector, as required
|
||||
message_single_str = qi::repeat(1)[message_str];
|
||||
message_strs = message_str_plural(qi::_r1) >> -message_strs(qi::_r1 + 1);
|
||||
// ^ enforces that indices must count up
|
||||
|
||||
// Detect whether we should read multiple messages or a single message by presence of `msgid_plural`
|
||||
message_plural = message_id_plural >> message_strs(0); // first line should be msgstr[0]
|
||||
message_singular = attr("") >> message_single_str;
|
||||
message = -message_context >> message_id >> (message_plural | message_singular);
|
||||
|
||||
/***
|
||||
* The remaining rules are not contributing to message -- their job is to consume comments leading up to the message,
|
||||
* keep track of if we saw a fuzzy marker, and to consume the entire file if only whitespace lines remain, whether or
|
||||
* not it ends in new-line.
|
||||
*
|
||||
* First, parse "ignored_comments",
|
||||
* message_preamble is the main rule of this section
|
||||
*/
|
||||
|
||||
/// Fuzzy: Expect comment of the form #, with literal `, fuzzy` in the list somewhere.
|
||||
/// We use a qi local to keep track of if we saw it, this avoids excessive backtracking
|
||||
fuzzy = lit('#') >> (&lit(','))[qi::_a = false] >> *(lit(',') >> -(lit(" fuzzy")[qi::_a = true]) >> *(char_ - '\n' - ',')) >> lit('\n') >> qi::eps(qi::_a);
|
||||
preamble_comment_line = comment_line >> lit('\n');
|
||||
|
||||
ignored_comments = *(*preamble_comment_line >> white_line >> lit('\n'));
|
||||
preamble_comment_block = *preamble_comment_line >> -comment_line;
|
||||
// ^ if po-file ends in a comment without eol we should still consume it
|
||||
message_preamble = (fuzzy >> preamble_comment_block >> attr(true)) | (preamble_comment_line >> message_preamble) | (-comment_line >> attr(false));
|
||||
// ^ if we find fuzzy, short cut out of this test ^ consume one comment line and repeat ^ didn't find fuzzy, return false
|
||||
// ^ note: no backtrack after fuzzy... ^ note: no backtrack after comment line... and consume trailing comment
|
||||
// preamble_comment_block is nullable message_preamble is nullable
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_PO_GRAMMAR_HPP_INCLUDED
|
62
src/spirit_po/po_message.hpp
Normal file
62
src/spirit_po/po_message.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_PO_MESSAGE_HPP_INCLUDED
|
||||
#define SPIRIT_PO_PO_MESSAGE_HPP_INCLUDED
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace spirit_po {
|
||||
|
||||
typedef std::pair<std::string, std::vector<std::string>> plural_and_strings_type;
|
||||
|
||||
struct po_message {
|
||||
boost::optional<std::string> context;
|
||||
std::string id;
|
||||
plural_and_strings_type plural_and_strings;
|
||||
|
||||
std::size_t line_no;
|
||||
|
||||
// Get the 'id_plural', 'strings' fields from the pair.
|
||||
// It is arranged as a pair here to allow for simpler parsing with spirit attributes.
|
||||
std::string & id_plural() { return plural_and_strings.first; }
|
||||
const std::string & id_plural() const { return plural_and_strings.first; }
|
||||
|
||||
std::vector<std::string> & strings() { return plural_and_strings.second; }
|
||||
const std::vector<std::string> & strings() const { return plural_and_strings.second; }
|
||||
|
||||
// Check if message is plural. We do this for now by testing msgid_plural.size().
|
||||
// Recommended to use this method in case we change it in the future.
|
||||
bool is_plural() const { return static_cast<bool>(id_plural().size()); }
|
||||
};
|
||||
|
||||
/***
|
||||
* Debug printer
|
||||
*/
|
||||
#ifdef SPIRIT_PO_DEBUG
|
||||
inline std::string debug_string(const po_message & msg) {
|
||||
std::string result = "{\n";
|
||||
if (msg.context) {
|
||||
result += " context: \"" + *msg.context + "\"\n";
|
||||
}
|
||||
result += " id: \"" + msg.id + "\"\n";
|
||||
result += " id_plural: \"" + msg.id_plural() + "\"\n";
|
||||
result += " strings: { ";
|
||||
for (uint i = 0; i < msg.strings().size(); ++i) {
|
||||
if (i) { result += ", "; }
|
||||
result += '"' + msg.strings()[i] + '"';
|
||||
}
|
||||
result += " }\n";
|
||||
result += "}";
|
||||
return result;
|
||||
}
|
||||
#endif // SPIRIT_PO_DEBUG
|
||||
|
||||
} // end namespace spirit_po
|
||||
|
||||
#endif // SPIRIT_PO_PO_MESSAGE_HPP_INCLUDED
|
26
src/spirit_po/po_message_adapted.hpp
Normal file
26
src/spirit_po/po_message_adapted.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_PO_MESSAGE_ADAPTED_HPP_INCLUDED
|
||||
#define SPIRIT_PO_PO_MESSAGE_ADAPTED_HPP_INCLUDED
|
||||
|
||||
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
|
||||
#include <boost/fusion/include/adapt_struct.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <spirit_po/po_message.hpp>
|
||||
|
||||
BOOST_FUSION_ADAPT_STRUCT(
|
||||
spirit_po::po_message,
|
||||
(boost::optional<std::string>, context)
|
||||
(std::string, id)
|
||||
(spirit_po::plural_and_strings_type, plural_and_strings))
|
||||
|
||||
#endif // SPIRIT_PO_PO_MESSAGE_ADAPTED_HPP_INCLUDED
|
13
src/spirit_po/version.hpp
Normal file
13
src/spirit_po/version.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
// (C) Copyright 2015 - 2016 Christopher Beck
|
||||
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef SPIRIT_PO_VERSION_HPP_INCLUDED
|
||||
#define SPIRIT_PO_VERSION_HPP_INCLUDED
|
||||
|
||||
#define SPIRIT_PO_VERSION_MAJOR 1
|
||||
#define SPIRIT_PO_VERSION_MINOR 0
|
||||
#define SPIRIT_PO_VERSION_PATCH 1
|
||||
|
||||
#endif // SPIRIT_PO_VERSION_HPP_INCLUDED
|
|
@ -13,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#define GETTEXT_DOMAIN "wesnoth-test"
|
||||
//#include <libintl.h>
|
||||
|
||||
#include "tests/utils/game_config_manager.hpp"
|
||||
|
||||
|
|
|
@ -347,7 +347,6 @@ static int process_command_args(const commandline_options& cmdline_opts) {
|
|||
}
|
||||
if(cmdline_opts.data_dir) {
|
||||
const std::string datadir = *cmdline_opts.data_dir;
|
||||
std::cerr << "Overriding data directory with " << datadir << std::endl;
|
||||
#ifdef _WIN32
|
||||
// use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
|
||||
if(datadir.c_str()[1] == ':') {
|
||||
|
@ -358,6 +357,8 @@ static int process_command_args(const commandline_options& cmdline_opts) {
|
|||
} else {
|
||||
game_config::path = filesystem::get_cwd() + '/' + datadir;
|
||||
}
|
||||
game_config::path = filesystem::normalize_path(game_config::path, true, true);
|
||||
std::cerr << "Overriding data directory with " << game_config::path << std::endl;
|
||||
|
||||
if(!filesystem::is_directory(game_config::path)) {
|
||||
std::cerr << "Could not find directory '" << game_config::path << "'\n";
|
||||
|
|
Loading…
Add table
Reference in a new issue