Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*
|
||||
* This allows converting `std::tm datetime` structures to localized date and
|
||||
* time strings. The localization is based on the CLDR from the current locale.
|
||||
*/
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
namespace blender::date_string {
|
||||
|
||||
enum class DateFormat : uint8_t {
|
||||
/** Convention based on output language. */
|
||||
Default = 0,
|
||||
/** `dd/mm/yyyy`. */
|
||||
LE_Slash = 1,
|
||||
/** `dd.mm.yyyy`. */
|
||||
LE_Dot = 2,
|
||||
/** `dd-mm-yyyy`. */
|
||||
LE_Dash = 3,
|
||||
/** `mm/dd/yyyy`. */
|
||||
ME_Slash = 4,
|
||||
/** `yyyy/mm/dd`. */
|
||||
BE_Slash = 5,
|
||||
/** `yyyy.mm.dd`. */
|
||||
BE_Dot = 6,
|
||||
/** `yyyy-mm-dd`. */
|
||||
BE_Dash = 7,
|
||||
};
|
||||
|
||||
enum class TimeFormat : uint8_t {
|
||||
/** 23:59 */
|
||||
H24 = 0,
|
||||
/** 11:59 PM */
|
||||
H12 = 1,
|
||||
};
|
||||
|
||||
std::string date(const std::tm &date_time,
|
||||
StringRef locale_iso = {},
|
||||
DateFormat format = DateFormat::Default);
|
||||
|
||||
std::string time(const std::tm &date_time, TimeFormat format = TimeFormat::H24);
|
||||
|
||||
std::string datetime(const std::tm &date_time,
|
||||
StringRef locale_iso = {},
|
||||
DateFormat date_format = DateFormat::Default,
|
||||
TimeFormat time_format = TimeFormat::H24,
|
||||
const std::tm *now = nullptr,
|
||||
StringRef today = {},
|
||||
StringRef yesterday = {});
|
||||
|
||||
} // namespace blender::date_string
|
||||
54
blender-5.2.0/source/blender/blentranslation/BLT_lang.hh
Normal file
54
blender-5.2.0/source/blender/blentranslation/BLT_lang.hh
Normal file
@@ -0,0 +1,54 @@
|
||||
/* SPDX-FileCopyrightText: 2011 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct EnumPropertyItem;
|
||||
|
||||
/* Search the path directory to the locale files, this try all
|
||||
* the case for Linux, Win and Mac.
|
||||
* Also dynamically builds locales and locales' menu from "languages" text file.
|
||||
*/
|
||||
void BLT_lang_init();
|
||||
|
||||
/* Free languages and locales_menu arrays created by BLT_lang_init. */
|
||||
void BLT_lang_free();
|
||||
|
||||
/* Set the current locale. */
|
||||
void BLT_lang_set(const char *);
|
||||
/* Get the current locale ([partial] ISO code, e.g. `pt_BR`). */
|
||||
const char *BLT_lang_get();
|
||||
|
||||
/* Get locale's elements (if relevant pointer is not NULL and element actually exists, e.g.
|
||||
* if there is no variant, *variant and *language_variant will always be NULL).
|
||||
* Non-null elements are always MEM_new_uninitialized'ed, it's the caller's responsibility to free
|
||||
* them. NOTE: Always available, even in non-WITH_INTERNATIONAL builds.
|
||||
*/
|
||||
/**
|
||||
* Get locale's elements (if relevant pointer is not NULL and element actually exists, e.g.
|
||||
* if there is no variant,
|
||||
* *variant and *language_variant will always be NULL).
|
||||
* Non-null elements are always MEM_new_uninitialized'ed, it's the caller's responsibility to free
|
||||
* them.
|
||||
*
|
||||
* \note Keep that one always available, you never know,
|
||||
* may become useful even in no #WITH_INTERNATIONAL context.
|
||||
*/
|
||||
void BLT_lang_locale_explode(const char *locale,
|
||||
char **language,
|
||||
char **country,
|
||||
char **variant,
|
||||
char **language_country,
|
||||
char **language_variant);
|
||||
|
||||
/* Get EnumPropertyItem's for translations menu. */
|
||||
const EnumPropertyItem *BLT_lang_RNA_enum_properties();
|
||||
|
||||
} // namespace blender
|
||||
246
blender-5.2.0/source/blender/blentranslation/BLT_translation.hh
Normal file
246
blender-5.2.0/source/blender/blentranslation/BLT_translation.hh
Normal file
@@ -0,0 +1,246 @@
|
||||
/* SPDX-FileCopyrightText: 2011 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#define TEXT_DOMAIN_NAME "blender"
|
||||
|
||||
namespace blender {
|
||||
|
||||
bool BLT_is_default_context(StringRef msgctxt);
|
||||
const char *BLT_pgettext(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_pgettext(StringRef msgctxt, StringRef msgid);
|
||||
|
||||
/* Translation */
|
||||
/* - iface includes buttons in the user interface: short labels displayed in windows, panels,
|
||||
* menus.
|
||||
* - tooltips only include the popup tooltips when hovering a button.
|
||||
* - report is for longer, additional information displayed in the UI, such as error messages.
|
||||
* - new_dataname is the actual user-created data such as objects, meshes, etc. */
|
||||
bool BLT_translate();
|
||||
bool BLT_translate_iface();
|
||||
bool BLT_translate_tooltips();
|
||||
bool BLT_translate_reports();
|
||||
bool BLT_translate_new_dataname();
|
||||
const char *BLT_translate_do(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_translate_do(StringRef msgctxt, StringRef msgid);
|
||||
const char *BLT_translate_do_iface(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_translate_do_iface(StringRef msgctxt, StringRef msgid);
|
||||
const char *BLT_translate_do_tooltip(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_translate_do_tooltip(StringRef msgctxt, StringRef msgid);
|
||||
const char *BLT_translate_do_report(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_translate_do_report(StringRef msgctxt, StringRef msgid);
|
||||
const char *BLT_translate_do_new_dataname(const char *msgctxt, const char *msgid);
|
||||
StringRef BLT_translate_do_new_dataname(StringRef msgctxt, StringRef msgid);
|
||||
|
||||
/* The "translation-marker" macro. */
|
||||
#define N_(msgid) msgid
|
||||
#define CTX_N_(context, msgid) msgid
|
||||
|
||||
/* These macros should be used everywhere in UI code. */
|
||||
// #define _(msgid) BLT_gettext(msgid)
|
||||
#define IFACE_(msgid) BLT_translate_do_iface(NULL, msgid)
|
||||
#define TIP_(msgid) BLT_translate_do_tooltip(NULL, msgid)
|
||||
#define RPT_(msgid) BLT_translate_do_report(NULL, msgid)
|
||||
#define DATA_(msgid) BLT_translate_do_new_dataname(NULL, msgid)
|
||||
#define CTX_IFACE_(context, msgid) BLT_translate_do_iface(context, msgid)
|
||||
#define CTX_TIP_(context, msgid) BLT_translate_do_tooltip(context, msgid)
|
||||
#define CTX_RPT_(context, msgid) BLT_translate_do_report(context, msgid)
|
||||
#define CTX_DATA_(context, msgid) BLT_translate_do_new_dataname(context, msgid)
|
||||
|
||||
/* Helper macro, when we want to define a same msgid for multiple msgctxt...
|
||||
* Does nothing in C, but is "parsed" by our i18n py tools.
|
||||
* XXX Currently limited to at most 16 contexts at once
|
||||
* (but you can call it several times with the same msgid, should you need more contexts!).
|
||||
*/
|
||||
#define BLT_I18N_MSGID_MULTI_CTXT(msgid, ...)
|
||||
|
||||
/******************************************************************************
|
||||
* All i18n contexts must be defined here.
|
||||
* This is a nice way to be sure not to use a context twice for different
|
||||
* things, and limit the number of existing contexts!
|
||||
* WARNING! Contexts should not be longer than BKE_ST_MAXNAME - 1!
|
||||
*/
|
||||
|
||||
/* Default, void context.
|
||||
* WARNING! The "" context is not the same as no (NULL) context at mo/boost::locale level!
|
||||
* NOTE: We translate BLT_I18NCONTEXT_DEFAULT as BLT_I18NCONTEXT_DEFAULT_BPY in Python,
|
||||
* as we can't use "natural" None value in rna string properties... :/
|
||||
* The void string "" is also interpreted as BLT_I18NCONTEXT_DEFAULT.
|
||||
* For performance reason, we only use the first char to detect this context,
|
||||
* so other contexts should never start with the same char!
|
||||
*/
|
||||
#define BLT_I18NCONTEXT_DEFAULT NULL
|
||||
#define BLT_I18NCONTEXT_DEFAULT_BPYRNA "*"
|
||||
|
||||
/* Default context for operator names/labels. */
|
||||
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT "Operator"
|
||||
|
||||
/* Context for events/keymaps (necessary, since those often use one or two letters,
|
||||
* easy to get collisions with other areas...). */
|
||||
#define BLT_I18NCONTEXT_UI_EVENTS "UI_Events_KeyMaps"
|
||||
|
||||
/* Mark the msgid applies to several elements
|
||||
* (needed in some cases, as English adjectives have no plural mark :( ). */
|
||||
#define BLT_I18NCONTEXT_PLURAL "Plural"
|
||||
|
||||
/* Some words can be either countable or uncountable in English, but translate to different words
|
||||
* in other languages. An example is "Amount", which can refer to "a number of things", countable,
|
||||
* or "a quantity or volume", uncountable. */
|
||||
#define BLT_I18NCONTEXT_COUNTABLE "Countable"
|
||||
|
||||
/* Special cases when translation cannot be avoided, for example in an interface where some props
|
||||
* are built-in (translatable) and others are user-defined (non-translatable), but we don't know
|
||||
* which ones in advance.
|
||||
* It allows specifying explicitly that translation should not occur for user data when building
|
||||
* the UI. */
|
||||
#define BLT_I18NCONTEXT_NO_TRANSLATION "Do not translate"
|
||||
|
||||
/* ID-types contexts. */
|
||||
/* WARNING! Keep it in sync with ID-types in `blenkernel/intern/idtype.cc`. */
|
||||
#define BLT_I18NCONTEXT_ID_ACTION "Action"
|
||||
#define BLT_I18NCONTEXT_ID_ANIMATION "Animation"
|
||||
#define BLT_I18NCONTEXT_ID_ARMATURE "Armature"
|
||||
#define BLT_I18NCONTEXT_ID_BRUSH "Brush"
|
||||
#define BLT_I18NCONTEXT_ID_CACHEFILE "CacheFile"
|
||||
#define BLT_I18NCONTEXT_ID_CAMERA "Camera"
|
||||
#define BLT_I18NCONTEXT_ID_COLLECTION "Collection"
|
||||
#define BLT_I18NCONTEXT_ID_CURVES "Curves"
|
||||
#define BLT_I18NCONTEXT_ID_CURVE_LEGACY "Curve"
|
||||
#define BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE "FreestyleLineStyle"
|
||||
#define BLT_I18NCONTEXT_ID_GPENCIL "GPencil"
|
||||
#define BLT_I18NCONTEXT_ID_ID "ID"
|
||||
#define BLT_I18NCONTEXT_ID_IMAGE "Image"
|
||||
// #define BLT_I18NCONTEXT_ID_IPO "Ipo" /* DEPRECATED */
|
||||
#define BLT_I18NCONTEXT_ID_LATTICE "Lattice"
|
||||
#define BLT_I18NCONTEXT_ID_LIBRARY "Library"
|
||||
#define BLT_I18NCONTEXT_ID_LIGHT "Light"
|
||||
#define BLT_I18NCONTEXT_ID_LIGHTPROBE "LightProbe"
|
||||
#define BLT_I18NCONTEXT_ID_MASK "Mask"
|
||||
#define BLT_I18NCONTEXT_ID_MATERIAL "Material"
|
||||
#define BLT_I18NCONTEXT_ID_MESH "Mesh"
|
||||
#define BLT_I18NCONTEXT_ID_METABALL "Metaball"
|
||||
#define BLT_I18NCONTEXT_ID_MOVIECLIP "MovieClip"
|
||||
#define BLT_I18NCONTEXT_ID_NODETREE "NodeTree"
|
||||
#define BLT_I18NCONTEXT_ID_OBJECT "Object"
|
||||
#define BLT_I18NCONTEXT_ID_PAINTCURVE "PaintCurve"
|
||||
#define BLT_I18NCONTEXT_ID_PALETTE "Palette"
|
||||
#define BLT_I18NCONTEXT_ID_PARTICLESETTINGS "ParticleSettings"
|
||||
#define BLT_I18NCONTEXT_ID_POINTCLOUD "PointCloud"
|
||||
#define BLT_I18NCONTEXT_ID_SCENE "Scene"
|
||||
#define BLT_I18NCONTEXT_ID_SCREEN "Screen"
|
||||
#define BLT_I18NCONTEXT_ID_SEQUENCE "Sequence"
|
||||
#define BLT_I18NCONTEXT_ID_SHAPEKEY "Key"
|
||||
#define BLT_I18NCONTEXT_ID_SIMULATION "Simulation"
|
||||
#define BLT_I18NCONTEXT_ID_SOUND "Sound"
|
||||
#define BLT_I18NCONTEXT_ID_SPEAKER "Speaker"
|
||||
#define BLT_I18NCONTEXT_ID_TEXT "Text"
|
||||
#define BLT_I18NCONTEXT_ID_TEXTURE "Texture"
|
||||
#define BLT_I18NCONTEXT_ID_VFONT "VFont"
|
||||
#define BLT_I18NCONTEXT_ID_VOLUME "Volume"
|
||||
#define BLT_I18NCONTEXT_ID_WINDOWMANAGER "WindowManager"
|
||||
#define BLT_I18NCONTEXT_ID_WORKSPACE "WorkSpace"
|
||||
#define BLT_I18NCONTEXT_ID_WORLD "World"
|
||||
|
||||
/* Editors-types contexts. */
|
||||
#define BLT_I18NCONTEXT_EDITOR_FILEBROWSER "File browser"
|
||||
#define BLT_I18NCONTEXT_EDITOR_PREFERENCES "Preferences"
|
||||
#define BLT_I18NCONTEXT_EDITOR_PYTHON_CONSOLE "Python console"
|
||||
#define BLT_I18NCONTEXT_EDITOR_VIEW3D "View3D"
|
||||
|
||||
/* Generic contexts. */
|
||||
#define BLT_I18NCONTEXT_AMOUNT "Amount"
|
||||
#define BLT_I18NCONTEXT_COLOR "Color"
|
||||
#define BLT_I18NCONTEXT_CONSTRAINT "Constraint"
|
||||
#define BLT_I18NCONTEXT_MODIFIER "Modifier"
|
||||
#define BLT_I18NCONTEXT_NAVIGATION "Navigation"
|
||||
#define BLT_I18NCONTEXT_RENDER_LAYER "Render Layer"
|
||||
#define BLT_I18NCONTEXT_TIME "Time"
|
||||
#define BLT_I18NCONTEXT_UNIT "Unit"
|
||||
|
||||
/* Helper for bpy.app.i18n object... */
|
||||
struct BLT_i18n_contexts_descriptor {
|
||||
const char *c_id;
|
||||
const char *py_id;
|
||||
const char *value;
|
||||
};
|
||||
|
||||
#define BLT_I18NCONTEXTS_ITEM(ctxt_id, py_id) {#ctxt_id, py_id, ctxt_id}
|
||||
|
||||
#define BLT_I18NCONTEXTS_DESC \
|
||||
{ \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_DEFAULT, "default_real"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_DEFAULT_BPYRNA, "default"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "operator_default"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_UI_EVENTS, "ui_events_keymaps"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_PLURAL, "plural"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_COUNTABLE, "countable"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_ACTION, "id_action"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_ARMATURE, "id_armature"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_NO_TRANSLATION, "no_translation"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_BRUSH, "id_brush"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CACHEFILE, "id_cachefile"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CAMERA, "id_camera"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_COLLECTION, "id_collection"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVES, "id_curves"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVE_LEGACY, "id_curve"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, "id_fs_linestyle"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_GPENCIL, "id_gpencil"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_ID, "id_id"), \
|
||||
BLT_I18NCONTEXTS_ITEM( \
|
||||
BLT_I18NCONTEXT_ID_IMAGE, \
|
||||
"id_image"), /* BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_IPO, "id_ipo"), */ \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_LATTICE, "id_lattice"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_LIBRARY, "id_library"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_LIGHT, "id_light"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_LIGHTPROBE, "id_lightprobe"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_MASK, "id_mask"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_MATERIAL, "id_material"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_MESH, "id_mesh"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_METABALL, "id_metaball"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_MOVIECLIP, "id_movieclip"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_NODETREE, "id_nodetree"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_OBJECT, "id_object"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_PAINTCURVE, "id_paintcurve"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_PALETTE, "id_palette"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_PARTICLESETTINGS, "id_particlesettings"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_POINTCLOUD, "id_pointcloud"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SCENE, "id_scene"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SCREEN, "id_screen"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SEQUENCE, "id_sequence"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SHAPEKEY, "id_shapekey"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SIMULATION, "id_simulation"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SOUND, "id_sound"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_SPEAKER, "id_speaker"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_TEXT, "id_text"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_TEXTURE, "id_texture"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_VFONT, "id_vfont"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_VOLUME, "id_volume"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WINDOWMANAGER, "id_windowmanager"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WORKSPACE, "id_workspace"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_WORLD, "id_world"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_EDITOR_FILEBROWSER, "editor_filebrowser"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_EDITOR_PYTHON_CONSOLE, "editor_python_console"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_EDITOR_PREFERENCES, "editor_preferences"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_EDITOR_VIEW3D, "editor_view3d"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_AMOUNT, "amount"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_COLOR, "color"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_CONSTRAINT, "constraint"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_MODIFIER, "modifier"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_NAVIGATION, "navigation"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_RENDER_LAYER, "render_layer"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_TIME, "time"), \
|
||||
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_UNIT, "unit"), \
|
||||
{ \
|
||||
NULL, NULL, NULL \
|
||||
} \
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
65
blender-5.2.0/source/blender/blentranslation/CMakeLists.txt
Normal file
65
blender-5.2.0/source/blender/blentranslation/CMakeLists.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
# SPDX-FileCopyrightText: 2008 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
PUBLIC .
|
||||
../makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
)
|
||||
|
||||
set(SRC
|
||||
intern/blt_lang.cc
|
||||
intern/blt_translation.cc
|
||||
intern/date_string.cc
|
||||
|
||||
BLT_date_string.hh
|
||||
BLT_lang.hh
|
||||
BLT_translation.hh
|
||||
)
|
||||
|
||||
if(WITH_INTERNATIONAL)
|
||||
list(APPEND SRC
|
||||
intern/messages.cc
|
||||
intern/messages.hh
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND SRC
|
||||
intern/messages_apple.mm
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::intern::clog
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
)
|
||||
|
||||
if(WITH_INTERNATIONAL)
|
||||
add_definitions(-DWITH_INTERNATIONAL)
|
||||
endif()
|
||||
|
||||
if(WITH_PYTHON)
|
||||
add_definitions(-DWITH_PYTHON)
|
||||
list(APPEND INC
|
||||
../python
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_INPUT_IME)
|
||||
add_definitions(-DWITH_INPUT_IME)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_blentranslation "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_library(bf::blentranslation ALIAS bf_blentranslation)
|
||||
|
||||
if(WITH_INTERNATIONAL)
|
||||
add_subdirectory(msgfmt)
|
||||
endif()
|
||||
349
blender-5.2.0/source/blender/blentranslation/intern/blt_lang.cc
Normal file
349
blender-5.2.0/source/blender/blentranslation/intern/blt_lang.cc
Normal file
@@ -0,0 +1,349 @@
|
||||
/* SPDX-FileCopyrightText: 2008 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*
|
||||
* Main internationalization functions to set the locale and query available languages.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <clocale>
|
||||
#endif
|
||||
|
||||
#include "RNA_types.hh"
|
||||
|
||||
#include "BLT_lang.hh" /* own include */
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BKE_appdir.hh"
|
||||
|
||||
#include "DNA_userdef_types.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
# include "BLI_fileops.h"
|
||||
# include "BLI_linklist.h"
|
||||
# include "messages.hh"
|
||||
#endif
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"translation"};
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
|
||||
/* Locale options. */
|
||||
static const char **locales = nullptr;
|
||||
static int num_locales = 0;
|
||||
static EnumPropertyItem *locales_menu = nullptr;
|
||||
static int num_locales_menu = 0;
|
||||
|
||||
static void free_locales()
|
||||
{
|
||||
if (locales_menu) {
|
||||
int idx = num_locales_menu - 1; /* Last item does not need to be freed! */
|
||||
while (idx--) {
|
||||
MEM_delete(locales_menu[idx].identifier); /* Also frees locales's relevant value! */
|
||||
MEM_delete(locales_menu[idx].name);
|
||||
MEM_delete(locales_menu[idx].description);
|
||||
}
|
||||
}
|
||||
MEM_SAFE_DELETE(locales_menu);
|
||||
/* Allocated strings in #locales are shared with #locales_menu[idx].identifier, which are already
|
||||
* freed above, or are static strings. */
|
||||
MEM_SAFE_DELETE(locales);
|
||||
num_locales = num_locales_menu = 0;
|
||||
}
|
||||
|
||||
static void fill_locales()
|
||||
{
|
||||
std::optional<std::string> languages_path = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
|
||||
if (!languages_path.has_value()) {
|
||||
CLOG_WARN(&LOG, "'locale' data path for translations not found");
|
||||
return;
|
||||
}
|
||||
|
||||
free_locales();
|
||||
|
||||
char languages[FILE_MAX];
|
||||
BLI_path_join(languages, FILE_MAX, languages_path->c_str(), "languages");
|
||||
|
||||
LinkNode *lines = BLI_file_read_as_lines(languages);
|
||||
LinkNode *line = lines;
|
||||
int idx = 0;
|
||||
|
||||
/* This whole "parsing" code is a bit weak, in that it expects strictly formatted input file...
|
||||
* Should not be a problem, though, as this file is script-generated! */
|
||||
|
||||
/* First loop to find highest locale ID */
|
||||
while (line) {
|
||||
int t;
|
||||
char *str = (char *)line->link;
|
||||
if (ELEM(str[0], '#', '\0')) {
|
||||
line = line->next;
|
||||
continue; /* Comment or void... */
|
||||
}
|
||||
t = atoi(str);
|
||||
if (t >= num_locales) {
|
||||
num_locales = t + 1;
|
||||
}
|
||||
num_locales_menu++;
|
||||
line = line->next;
|
||||
}
|
||||
num_locales_menu++; /* The "closing" void item... */
|
||||
|
||||
/* And now, build locales and locale_menu! */
|
||||
locales_menu = MEM_new_array_zeroed<EnumPropertyItem>(num_locales_menu, __func__);
|
||||
line = lines;
|
||||
/* Do not allocate locales with zero-sized mem,
|
||||
* as LOCALE macro uses nullptr locales as invalid marker! */
|
||||
if (num_locales > 0) {
|
||||
locales = MEM_new_array_zeroed<const char *>(num_locales, __func__);
|
||||
while (line) {
|
||||
const char *loc, *desc, *sep1, *sep2, *sep3;
|
||||
|
||||
char *str = (char *)line->link;
|
||||
if (ELEM(str[0], '#', '\0')) {
|
||||
line = line->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int id = atoi(str);
|
||||
sep1 = strchr(str, ':');
|
||||
if (sep1) {
|
||||
sep1++;
|
||||
sep2 = strchr(sep1, ':');
|
||||
if (sep2) {
|
||||
locales_menu[idx].value = id;
|
||||
locales_menu[idx].icon = 0;
|
||||
locales_menu[idx].name = BLI_strdupn(sep1, sep2 - sep1);
|
||||
|
||||
sep2++;
|
||||
sep3 = strchr(sep2, ':');
|
||||
|
||||
if (sep3) {
|
||||
locales_menu[idx].identifier = loc = BLI_strdupn(sep2, sep3 - sep2);
|
||||
|
||||
sep3++;
|
||||
desc = BLI_sprintfN("Locale code: %s. Translation progress: %s", loc, sep3);
|
||||
}
|
||||
else {
|
||||
locales_menu[idx].identifier = loc = BLI_strdup(sep2);
|
||||
desc = BLI_strdup(sep2);
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
/* The DEFAULT/Automatic item... */
|
||||
if (loc[0] != '\0') {
|
||||
MEM_delete(desc); /* Not used here. */
|
||||
locales[id] = "";
|
||||
/* Keep this tip in sync with the one in rna_userdef
|
||||
* (rna_enum_language_default_items). */
|
||||
locales_menu[idx].description = BLI_strdup(
|
||||
"Automatically choose the system-defined language if available, or fall-back to "
|
||||
"English (US)");
|
||||
}
|
||||
/* Menu "label", not to be stored in locales!
|
||||
* NOTE: Not used since Blender 4.5. */
|
||||
else {
|
||||
locales_menu[idx].description = desc;
|
||||
}
|
||||
}
|
||||
else {
|
||||
locales[id] = loc;
|
||||
locales_menu[idx].description = desc;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
line = line->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add closing item to menu! */
|
||||
locales_menu[idx].identifier = nullptr;
|
||||
locales_menu[idx].value = locales_menu[idx].icon = 0;
|
||||
locales_menu[idx].name = locales_menu[idx].description = "";
|
||||
|
||||
BLI_file_free_lines(lines);
|
||||
}
|
||||
#endif /* WITH_INTERNATIONAL */
|
||||
|
||||
const EnumPropertyItem *BLT_lang_RNA_enum_properties()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return locales_menu;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BLT_lang_init()
|
||||
{
|
||||
/* Make sure LANG is correct and wouldn't cause #std::runtime_error. */
|
||||
#ifndef _WIN32
|
||||
/* TODO(sergey): This code only ensures LANG is set properly, so later when
|
||||
* Cycles will try to use file system API from boost there will be no runtime
|
||||
* exception generated by #std::locale() which _requires_ having proper LANG
|
||||
* set in the environment.
|
||||
*
|
||||
* Ideally we also need to ensure LC_ALL, LC_MESSAGES and others are also
|
||||
* set to a proper value, but currently it's not a huge deal and doesn't
|
||||
* cause any headache.
|
||||
*
|
||||
* Would also be good to find nicer way to check if LANG is correct.
|
||||
*/
|
||||
const char *lang = BLI_getenv("LANG");
|
||||
if (lang != nullptr) {
|
||||
char *old_locale = setlocale(LC_ALL, nullptr);
|
||||
/* Make a copy so subsequent #setlocale() doesn't interfere. */
|
||||
old_locale = BLI_strdup(old_locale);
|
||||
if (setlocale(LC_ALL, lang) == nullptr) {
|
||||
setenv("LANG", "C", 1);
|
||||
CLOG_WARN(&LOG, "Falling back to standard locale (\"C\")");
|
||||
}
|
||||
setlocale(LC_ALL, old_locale);
|
||||
MEM_delete(old_locale);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
fill_locales();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BLT_lang_free()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
locale::free();
|
||||
free_locales();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
static uint lang_from_userdef()
|
||||
{
|
||||
const uint language = uint(U.language);
|
||||
if ((language >= ULANGUAGE_AUTO) && (language < num_locales)) {
|
||||
return language;
|
||||
}
|
||||
return uint(ULANGUAGE_ENGLISH);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
# define ULANGUAGE lang_from_userdef()
|
||||
# define LOCALE(_id) (locales ? locales[(_id)] : "")
|
||||
#endif
|
||||
|
||||
void BLT_lang_set(const char *str)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
int ulang = ULANGUAGE;
|
||||
std::string locale_name = str ? str : LOCALE(ulang);
|
||||
|
||||
/* #locale assumes UTF8, no need to put it in the name. */
|
||||
const std::optional<std::string> messagepath = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
|
||||
locale::init(locale_name, {TEXT_DOMAIN_NAME}, {messagepath.value_or("")});
|
||||
|
||||
#else
|
||||
(void)str;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_lang_get()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate()) {
|
||||
const char *locale = LOCALE(ULANGUAGE);
|
||||
if (locale[0] == '\0') {
|
||||
/* Default locale, we have to find which one we are actually using! */
|
||||
locale = locale::full_name();
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
return "en_US"; /* Kind of default locale in Blender when no translation enabled. */
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef LOCALE
|
||||
#undef ULANGUAGE
|
||||
|
||||
void BLT_lang_locale_explode(const char *locale,
|
||||
char **language,
|
||||
char **country,
|
||||
char **variant,
|
||||
char **language_country,
|
||||
char **language_variant)
|
||||
{
|
||||
const char *m1, *m2;
|
||||
char *_t = nullptr;
|
||||
|
||||
m1 = strchr(locale, '_');
|
||||
m2 = strchr(locale, '@');
|
||||
|
||||
if (language || language_variant) {
|
||||
if (m1 || m2) {
|
||||
_t = m1 ? BLI_strdupn(locale, m1 - locale) : BLI_strdupn(locale, m2 - locale);
|
||||
if (language) {
|
||||
*language = _t;
|
||||
}
|
||||
}
|
||||
else if (language) {
|
||||
*language = BLI_strdup(locale);
|
||||
}
|
||||
}
|
||||
if (country) {
|
||||
if (m1) {
|
||||
*country = m2 ? BLI_strdupn(m1 + 1, m2 - (m1 + 1)) : BLI_strdup(m1 + 1);
|
||||
}
|
||||
else {
|
||||
*country = nullptr;
|
||||
}
|
||||
}
|
||||
if (variant) {
|
||||
if (m2) {
|
||||
*variant = BLI_strdup(m2 + 1);
|
||||
}
|
||||
else {
|
||||
*variant = nullptr;
|
||||
}
|
||||
}
|
||||
if (language_country) {
|
||||
if (m1) {
|
||||
*language_country = m2 ? BLI_strdupn(locale, m2 - locale) : BLI_strdup(locale);
|
||||
}
|
||||
else {
|
||||
*language_country = nullptr;
|
||||
}
|
||||
}
|
||||
if (language_variant) {
|
||||
if (m2) {
|
||||
*language_variant = m1 ? BLI_strdupcat(_t, m2) : BLI_strdup(locale);
|
||||
}
|
||||
else {
|
||||
*language_variant = nullptr;
|
||||
}
|
||||
}
|
||||
if (_t && !language) {
|
||||
MEM_delete(_t);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,251 @@
|
||||
/* SPDX-FileCopyrightText: 2011 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*
|
||||
* Manages translation files and provides translation functions.
|
||||
* (which are optional and can be disabled as a preference).
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_userdef_types.h" /* For user settings. */
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
# include "BPY_extern.hh"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
# include "BLI_threads.h"
|
||||
# include "messages.hh"
|
||||
#endif /* WITH_INTERNATIONAL */
|
||||
|
||||
namespace blender {
|
||||
|
||||
bool BLT_is_default_context(const StringRef msgctxt)
|
||||
{
|
||||
/* We use the "short" test, a more complete one could be:
|
||||
* return (!msgctxt || !msgctxt[0] || STREQ(msgctxt, BLT_I18NCONTEXT_DEFAULT_BPYRNA))
|
||||
*/
|
||||
/* NOTE: trying without the void string check for now, it *should* not be necessary... */
|
||||
return (msgctxt.is_empty() || msgctxt[0] == BLT_I18NCONTEXT_DEFAULT_BPYRNA[0]);
|
||||
}
|
||||
|
||||
static std::optional<StringRefNull> pgettext(StringRef msgctxt, const StringRef msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (msgid.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (BLT_is_default_context(msgctxt)) {
|
||||
msgctxt = BLT_I18NCONTEXT_DEFAULT;
|
||||
}
|
||||
if (const std::optional<StringRefNull> translation = locale::translate(0, msgctxt, msgid)) {
|
||||
return translation;
|
||||
}
|
||||
# ifdef WITH_PYTHON
|
||||
return BPY_app_translations_py_pgettext(msgctxt, msgid);
|
||||
# else
|
||||
return std::nullopt;
|
||||
# endif
|
||||
#else
|
||||
UNUSED_VARS(msgctxt, msgid);
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_pgettext(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
const std::optional<StringRefNull> translation = pgettext(msgctxt, msgid);
|
||||
if (!translation) {
|
||||
return msgid;
|
||||
}
|
||||
return translation->c_str();
|
||||
}
|
||||
|
||||
StringRef BLT_pgettext(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
const std::optional<StringRefNull> translation = pgettext(msgctxt, msgid);
|
||||
if (!translation) {
|
||||
return msgid;
|
||||
}
|
||||
return *translation;
|
||||
}
|
||||
|
||||
bool BLT_translate()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return BLI_thread_is_main();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BLT_translate_iface()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return BLT_translate() && (U.transopts & USER_TR_IFACE);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BLT_translate_tooltips()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return BLT_translate() && (U.transopts & USER_TR_TOOLTIPS);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BLT_translate_reports()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return BLT_translate() && (U.transopts & USER_TR_REPORTS);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BLT_translate_new_dataname()
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
return BLT_translate() && (U.transopts & USER_TR_NEWDATANAME);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename StringT> StringT translate_do(StringT msgctxt, StringT msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate()) {
|
||||
return BLT_pgettext(msgctxt, msgid);
|
||||
}
|
||||
|
||||
return msgid;
|
||||
|
||||
#else
|
||||
(void)msgctxt;
|
||||
return msgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_translate_do(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
return translate_do(msgctxt, msgid);
|
||||
}
|
||||
|
||||
StringRef BLT_translate_do(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
return translate_do(msgctxt, msgid);
|
||||
}
|
||||
|
||||
template<typename StringT> StringT translate_do_iface(StringT msgctxt, StringT msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate_iface()) {
|
||||
return BLT_pgettext(msgctxt, msgid);
|
||||
}
|
||||
|
||||
return msgid;
|
||||
|
||||
#else
|
||||
(void)msgctxt;
|
||||
return msgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_translate_do_iface(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
return translate_do_iface(msgctxt, msgid);
|
||||
}
|
||||
|
||||
StringRef BLT_translate_do_iface(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
return translate_do_iface(msgctxt, msgid);
|
||||
}
|
||||
|
||||
template<typename StringT> StringT translate_do_tooltip(StringT msgctxt, StringT msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate_tooltips()) {
|
||||
return BLT_pgettext(msgctxt, msgid);
|
||||
}
|
||||
|
||||
return msgid;
|
||||
|
||||
#else
|
||||
(void)msgctxt;
|
||||
return msgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_translate_do_tooltip(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
return translate_do_tooltip(msgctxt, msgid);
|
||||
}
|
||||
|
||||
StringRef BLT_translate_do_tooltip(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
return translate_do_tooltip(msgctxt, msgid);
|
||||
}
|
||||
|
||||
template<typename StringT> StringT translate_do_report(StringT msgctxt, StringT msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate_reports()) {
|
||||
return BLT_pgettext(msgctxt, msgid);
|
||||
}
|
||||
|
||||
return msgid;
|
||||
|
||||
#else
|
||||
(void)msgctxt;
|
||||
return msgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_translate_do_report(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
return translate_do_report(msgctxt, msgid);
|
||||
}
|
||||
|
||||
StringRef BLT_translate_do_report(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
return translate_do_report(msgctxt, msgid);
|
||||
}
|
||||
|
||||
template<typename StringT> StringT translate_do_new_dataname(StringT msgctxt, StringT msgid)
|
||||
{
|
||||
#ifdef WITH_INTERNATIONAL
|
||||
if (BLT_translate_new_dataname()) {
|
||||
return BLT_pgettext(msgctxt, msgid);
|
||||
}
|
||||
|
||||
return msgid;
|
||||
|
||||
#else
|
||||
(void)msgctxt;
|
||||
return msgid;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *BLT_translate_do_new_dataname(const char *msgctxt, const char *msgid)
|
||||
{
|
||||
return translate_do_new_dataname(msgctxt, msgid);
|
||||
}
|
||||
|
||||
StringRef BLT_translate_do_new_dataname(StringRef msgctxt, StringRef msgid)
|
||||
{
|
||||
return translate_do_new_dataname(msgctxt, msgid);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,157 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BLT_date_string.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
namespace blender::date_string {
|
||||
|
||||
struct LocalePatterns {
|
||||
StringRef locale;
|
||||
StringRef date;
|
||||
};
|
||||
|
||||
static const LocalePatterns &get_locale_patterns(const StringRef locale_iso)
|
||||
{
|
||||
static constexpr std::array<LocalePatterns, 9> patterns = {{
|
||||
{"", "{d:02} {b} {Y}"}, /* default */
|
||||
{"en_US", "{d:02} {b} {Y}"}, /* English (US) */
|
||||
{"ar_EG", "{d:02} {b} {Y}"}, /* Arabic (Egypt) */
|
||||
{"zh_HANS", "{Y}年{m}月{d}日"}, /* Chinese (Simplified) */
|
||||
{"zh_HANT", "{Y}年{m}月{d}日"}, /* Chinese (Traditional) */
|
||||
{"hu_HU", "{Y}. {b} {d:02}"}, /* Hungarian */
|
||||
{"ja_JP", "{Y}年{m}月{d}日"}, /* Japanese */
|
||||
{"ko_KR", "{Y}년 {m}월 {d}일"}, /* Korean */
|
||||
{"ur", "{d:02} {b} {Y}"}, /* Urdu */
|
||||
}};
|
||||
|
||||
for (const LocalePatterns &pattern : patterns) {
|
||||
if (pattern.locale == locale_iso) {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback default pattern (index 0). */
|
||||
return patterns[0];
|
||||
}
|
||||
|
||||
std::string time(const std::tm &date_time, TimeFormat format)
|
||||
{
|
||||
std::string_view time_format_str;
|
||||
switch (format) {
|
||||
case TimeFormat::H24:
|
||||
time_format_str = "{H:02}:{M:02}";
|
||||
break;
|
||||
case TimeFormat::H12:
|
||||
time_format_str = "{I}:{M:02} {p}";
|
||||
}
|
||||
|
||||
return fmt::format(fmt::runtime(time_format_str),
|
||||
fmt::arg("H", date_time.tm_hour),
|
||||
fmt::arg("M", date_time.tm_min),
|
||||
fmt::arg("S", date_time.tm_sec),
|
||||
fmt::arg("I", (date_time.tm_hour % 12) == 0 ? 12 : (date_time.tm_hour % 12)),
|
||||
fmt::arg("p",
|
||||
(date_time.tm_hour < 12) ? CTX_IFACE_(BLT_I18NCONTEXT_TIME, "AM") :
|
||||
CTX_IFACE_(BLT_I18NCONTEXT_TIME, "PM")));
|
||||
}
|
||||
|
||||
std::string date(const std::tm &date_time, const StringRef locale_iso, DateFormat format)
|
||||
{
|
||||
static constexpr std::array<StringRef, 12> months = {CTX_N_(BLT_I18NCONTEXT_TIME, "Jan"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Feb"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Mar"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Apr"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "May"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Jun"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Jul"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Aug"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Sep"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Oct"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Nov"),
|
||||
CTX_N_(BLT_I18NCONTEXT_TIME, "Dec")};
|
||||
|
||||
BLI_assert(date_time.tm_mon >= 0 && date_time.tm_mon < 12);
|
||||
const int month_index = std::clamp(date_time.tm_mon, 0, 11);
|
||||
|
||||
std::string_view date_format_str;
|
||||
switch (format) {
|
||||
case DateFormat::Default: {
|
||||
const LocalePatterns &pattern = get_locale_patterns(locale_iso);
|
||||
date_format_str = pattern.date;
|
||||
break;
|
||||
}
|
||||
case DateFormat::LE_Slash:
|
||||
date_format_str = "{d:02}/{m:02}/{Y}";
|
||||
break;
|
||||
case DateFormat::LE_Dot:
|
||||
date_format_str = "{d:02}.{m:02}.{Y}";
|
||||
break;
|
||||
case DateFormat::LE_Dash:
|
||||
date_format_str = "{d:02}-{m:02}-{Y}";
|
||||
break;
|
||||
case DateFormat::ME_Slash:
|
||||
date_format_str = "{m:02}/{d:02}/{Y}";
|
||||
break;
|
||||
case DateFormat::BE_Slash:
|
||||
date_format_str = "{Y}/{m:02}/{d:02}";
|
||||
break;
|
||||
case DateFormat::BE_Dot:
|
||||
date_format_str = "{Y}.{m:02}.{d:02}";
|
||||
break;
|
||||
case DateFormat::BE_Dash:
|
||||
date_format_str = "{Y}-{m:02}-{d:02}";
|
||||
break;
|
||||
}
|
||||
|
||||
return fmt::format(fmt::runtime(date_format_str),
|
||||
fmt::arg("Y", date_time.tm_year + 1900),
|
||||
fmt::arg("m", date_time.tm_mon + 1),
|
||||
fmt::arg("b", CTX_IFACE_(BLT_I18NCONTEXT_TIME, months[month_index])),
|
||||
fmt::arg("d", date_time.tm_mday));
|
||||
}
|
||||
|
||||
std::string datetime(const std::tm &date_time,
|
||||
const StringRef locale_iso,
|
||||
DateFormat date_format,
|
||||
TimeFormat time_format,
|
||||
const std::tm *now,
|
||||
const StringRef today,
|
||||
const StringRef yesterday)
|
||||
{
|
||||
bool is_today = false;
|
||||
bool is_yesterday = false;
|
||||
if (now && !today.is_empty() && !yesterday.is_empty()) {
|
||||
is_today = (date_time.tm_yday == now->tm_yday && date_time.tm_year == now->tm_year);
|
||||
std::tm yesterday_tm = *now;
|
||||
yesterday_tm.tm_mday--;
|
||||
mktime(&yesterday_tm);
|
||||
is_yesterday = (date_time.tm_yday == yesterday_tm.tm_yday &&
|
||||
date_time.tm_year == yesterday_tm.tm_year);
|
||||
}
|
||||
|
||||
const std::string time_s = time(date_time, time_format);
|
||||
|
||||
if (is_today) {
|
||||
return fmt::format("{} {}", today, time_s);
|
||||
}
|
||||
if (is_yesterday) {
|
||||
return fmt::format("{} {}", yesterday, time_s);
|
||||
}
|
||||
const std::string date_s = date(date_time, locale_iso, date_format);
|
||||
return fmt::format("{} {}", date_s, time_s);
|
||||
}
|
||||
|
||||
} // namespace blender::date_string
|
||||
643
blender-5.2.0/source/blender/blentranslation/intern/messages.cc
Normal file
643
blender-5.2.0/source/blender/blentranslation/intern/messages.cc
Normal file
@@ -0,0 +1,643 @@
|
||||
/* SPDX-FileCopyrightText: 2009-2015 Artyom Beilis (Tonkikh)
|
||||
* SPDX-FileCopyrightText: 2021-2023 Alexander Grund
|
||||
* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: BSL-1.0
|
||||
*
|
||||
* Adapted from boost::locale */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*/
|
||||
|
||||
#include "messages.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include "BLI_winstuff.h"
|
||||
#endif
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender::locale {
|
||||
|
||||
static CLG_LogRef LOG = {"translation"};
|
||||
|
||||
/* Upper/lower case, intentionally restricted to ASCII. */
|
||||
|
||||
static constexpr bool is_upper_ascii(const char c)
|
||||
{
|
||||
return 'A' <= c && c <= 'Z';
|
||||
}
|
||||
|
||||
static constexpr bool is_lower_ascii(const char c)
|
||||
{
|
||||
return 'a' <= c && c <= 'z';
|
||||
}
|
||||
|
||||
static bool make_lower_ascii(char &c)
|
||||
{
|
||||
if (is_upper_ascii(c)) {
|
||||
c += 'a' - 'A';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool make_upper_ascii(char &c)
|
||||
{
|
||||
if (is_lower_ascii(c)) {
|
||||
c += 'A' - 'a';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr bool is_numeric_ascii(const char c)
|
||||
{
|
||||
return '0' <= c && c <= '9';
|
||||
}
|
||||
|
||||
/* Info about a locale. */
|
||||
|
||||
class Info {
|
||||
public:
|
||||
std::string language = "C";
|
||||
std::string script;
|
||||
std::string country;
|
||||
std::string variant;
|
||||
|
||||
Info(const StringRef locale_full_name)
|
||||
{
|
||||
std::string locale_name(locale_full_name);
|
||||
|
||||
/* If locale name not specified, try to get the appropriate one from the system. */
|
||||
#if defined(__APPLE__) && !defined(WITH_HEADLESS) && !defined(WITH_GHOST_SDL)
|
||||
if (locale_name.empty()) {
|
||||
locale_name = macos_user_locale();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (locale_name.empty()) {
|
||||
const char *lc_all = BLI_getenv("LC_ALL");
|
||||
if (lc_all) {
|
||||
locale_name = lc_all;
|
||||
}
|
||||
}
|
||||
if (locale_name.empty()) {
|
||||
const char *lang = BLI_getenv("LANG");
|
||||
if (lang) {
|
||||
locale_name = lang;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (locale_name.empty()) {
|
||||
char buf[128] = {};
|
||||
if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, buf, sizeof(buf)) != 0) {
|
||||
locale_name = buf;
|
||||
if (GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, buf, sizeof(buf)) != 0) {
|
||||
std::string region = buf;
|
||||
if (locale_name == "zh") {
|
||||
if (region == "TW" || region == "HK" || region == "MO") {
|
||||
/* Traditional for Taiwan, Hong Kong, Macau. */
|
||||
locale_name += "_HANT";
|
||||
}
|
||||
else {
|
||||
/* Simplified for all other areas. */
|
||||
locale_name += "_HANS";
|
||||
}
|
||||
}
|
||||
else {
|
||||
locale_name += "_" + region;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
parse_from_lang(locale_name);
|
||||
}
|
||||
|
||||
std::string to_full_name() const
|
||||
{
|
||||
std::string result = language;
|
||||
if (!script.empty()) {
|
||||
result += '_' + script;
|
||||
}
|
||||
if (!country.empty()) {
|
||||
result += '_' + country;
|
||||
}
|
||||
if (!variant.empty()) {
|
||||
result += '@' + variant;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Locale parsing. */
|
||||
bool parse_from_variant(const std::string_view input)
|
||||
{
|
||||
if (language == "C" || input.empty()) {
|
||||
return false;
|
||||
}
|
||||
variant = input;
|
||||
/* No assumptions, just make it lowercase. */
|
||||
for (char &c : variant) {
|
||||
make_lower_ascii(c);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_from_encoding(const std::string_view input)
|
||||
{
|
||||
const int64_t end = input.find_first_of('@');
|
||||
std::string tmp(input.substr(0, end));
|
||||
if (tmp.empty()) {
|
||||
return false;
|
||||
}
|
||||
/* tmp contains encoding, we ignore it. */
|
||||
if (end >= input.size()) {
|
||||
return true;
|
||||
}
|
||||
BLI_assert(input[end] == '@');
|
||||
return parse_from_variant(input.substr(end + 1));
|
||||
}
|
||||
|
||||
bool parse_from_country(const std::string_view input)
|
||||
{
|
||||
if (language == "C") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int64_t end = input.find_first_of("@.");
|
||||
std::string tmp(input.substr(0, end));
|
||||
if (tmp.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (char &c : tmp) {
|
||||
make_upper_ascii(c);
|
||||
}
|
||||
|
||||
/* If it's ALL uppercase ASCII, assume ISO 3166 country id. */
|
||||
if (std::find_if_not(tmp.begin(), tmp.end(), is_upper_ascii) != tmp.end()) {
|
||||
/* else handle special cases:
|
||||
* - en_US_POSIX is an alias for C
|
||||
* - M49 country code: 3 digits */
|
||||
if (language == "en" && tmp == "US_POSIX") {
|
||||
language = "C";
|
||||
tmp.clear();
|
||||
}
|
||||
else if (tmp.size() != 3u ||
|
||||
std::find_if_not(tmp.begin(), tmp.end(), is_numeric_ascii) != tmp.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
country = tmp;
|
||||
if (end >= input.size()) {
|
||||
return true;
|
||||
}
|
||||
if (input[end] == '.') {
|
||||
return parse_from_encoding(input.substr(end + 1));
|
||||
}
|
||||
BLI_assert(input[end] == '@');
|
||||
return parse_from_variant(input.substr(end + 1));
|
||||
}
|
||||
|
||||
bool parse_from_script(const std::string_view input)
|
||||
{
|
||||
const int64_t end = input.find_first_of("-_@.");
|
||||
std::string tmp(input.substr(0, end));
|
||||
/* Script is exactly 4 ASCII characters, otherwise it is not present. */
|
||||
if (tmp.length() != 4) {
|
||||
return parse_from_country(input);
|
||||
}
|
||||
|
||||
for (char &c : tmp) {
|
||||
if (!is_lower_ascii(c) && !make_lower_ascii(c)) {
|
||||
return parse_from_country(input);
|
||||
}
|
||||
}
|
||||
make_upper_ascii(tmp[0]); /* Capitalize first letter only. */
|
||||
script = tmp;
|
||||
|
||||
if (end >= input.size()) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(input[end], '-', '_')) {
|
||||
return parse_from_country(input.substr(end + 1));
|
||||
}
|
||||
if (input[end] == '.') {
|
||||
return parse_from_encoding(input.substr(end + 1));
|
||||
}
|
||||
BLI_assert(input[end] == '@');
|
||||
return parse_from_variant(input.substr(end + 1));
|
||||
}
|
||||
|
||||
bool parse_from_lang(const std::string_view input)
|
||||
{
|
||||
const int64_t end = input.find_first_of("-_@.");
|
||||
std::string tmp(input.substr(0, end));
|
||||
if (tmp.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (char &c : tmp) {
|
||||
if (!is_lower_ascii(c) && !make_lower_ascii(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!ELEM(tmp, "c", "posix")) { /* Keep default if C or POSIX. */
|
||||
language = tmp;
|
||||
}
|
||||
|
||||
if (end >= input.size()) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(input[end], '-', '_')) {
|
||||
return parse_from_script(input.substr(end + 1));
|
||||
}
|
||||
if (input[end] == '.') {
|
||||
return parse_from_encoding(input.substr(end + 1));
|
||||
}
|
||||
BLI_assert(input[end] == '@');
|
||||
return parse_from_variant(input.substr(end + 1));
|
||||
}
|
||||
};
|
||||
|
||||
/* .mo file reader. */
|
||||
|
||||
class MOFile {
|
||||
uint32_t keys_offset_ = 0;
|
||||
uint32_t translations_offset_ = 0;
|
||||
|
||||
Vector<char> data_;
|
||||
bool native_byteorder_ = false;
|
||||
size_t size_ = false;
|
||||
|
||||
std::string error_;
|
||||
|
||||
public:
|
||||
MOFile(const std::string &filepath)
|
||||
{
|
||||
FILE *file = BLI_fopen(filepath.c_str(), "rb");
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
const int64_t len = BLI_ftell(file);
|
||||
if (len >= 0) {
|
||||
fseek(file, 0, SEEK_SET);
|
||||
data_.resize(len);
|
||||
if (fread(data_.data(), 1, len, file) != len) {
|
||||
data_.clear();
|
||||
error_ = "Failed to read file";
|
||||
}
|
||||
}
|
||||
else {
|
||||
error_ = "Wrong file object";
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (error_.empty()) {
|
||||
read_data();
|
||||
}
|
||||
}
|
||||
|
||||
const char *key(int id)
|
||||
{
|
||||
const uint32_t off = get(keys_offset_ + id * 8 + 4);
|
||||
return data_.data() + off;
|
||||
}
|
||||
|
||||
StringRef value(int id)
|
||||
{
|
||||
const uint32_t len = get(translations_offset_ + id * 8);
|
||||
const uint32_t off = get(translations_offset_ + id * 8 + 4);
|
||||
if (len > data_.size() || off > data_.size() - len) {
|
||||
error_ = "Bad mo-file format";
|
||||
return "";
|
||||
}
|
||||
return StringRef(&data_[off], len);
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
|
||||
const std::string &error() const
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
void read_data()
|
||||
{
|
||||
if (data_.size() < 4) {
|
||||
error_ = "Invalid 'mo' file format - the file is too short";
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t magic;
|
||||
memcpy(&magic, data_.data(), sizeof(magic));
|
||||
if (magic == 0x950412de) {
|
||||
native_byteorder_ = true;
|
||||
}
|
||||
else if (magic == 0xde120495) {
|
||||
native_byteorder_ = false;
|
||||
}
|
||||
else {
|
||||
error_ = "Invalid file format - invalid magic number";
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read all format sizes. */
|
||||
size_ = get(8);
|
||||
keys_offset_ = get(12);
|
||||
translations_offset_ = get(16);
|
||||
}
|
||||
|
||||
uint32_t get(int offset)
|
||||
{
|
||||
if (offset > data_.size() - 4) {
|
||||
error_ = "Bad mo-file format";
|
||||
return 0;
|
||||
}
|
||||
uint32_t v;
|
||||
memcpy(&v, &data_[offset], 4);
|
||||
if (!native_byteorder_) {
|
||||
v = ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v & 0xFF0000) >> 8) |
|
||||
((v & 0xFF000000) >> 24);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
/* Message lookup key. */
|
||||
|
||||
struct MessageKeyRef {
|
||||
StringRef context;
|
||||
StringRef str;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->context, this->str);
|
||||
}
|
||||
};
|
||||
|
||||
struct MessageKey {
|
||||
std::string context;
|
||||
std::string str;
|
||||
|
||||
MessageKey(const StringRef c)
|
||||
{
|
||||
const size_t pos = c.find(char(4));
|
||||
if (pos == StringRef::not_found) {
|
||||
this->str = c;
|
||||
}
|
||||
else {
|
||||
this->context = c.substr(0, pos);
|
||||
this->str = c.substr(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(this->context, this->str);
|
||||
}
|
||||
|
||||
static uint64_t hash_as(const MessageKeyRef &key)
|
||||
{
|
||||
return key.hash();
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const MessageKey &a, const MessageKey &b)
|
||||
{
|
||||
return a.context == b.context && a.str == b.str;
|
||||
}
|
||||
|
||||
inline bool operator==(const MessageKeyRef &a, const MessageKey &b)
|
||||
{
|
||||
return a.context == b.context && a.str == b.str;
|
||||
}
|
||||
|
||||
/* Messages translation based on .mo files. */
|
||||
|
||||
class MOMessages {
|
||||
using Catalog = Map<MessageKey, std::string>;
|
||||
Vector<Catalog> catalogs_;
|
||||
std::string error_;
|
||||
|
||||
public:
|
||||
MOMessages(const Info &info,
|
||||
const Vector<std::string> &domains,
|
||||
const Vector<std::string> &paths)
|
||||
{
|
||||
const Vector<std::string> catalog_paths = get_catalog_paths(info, paths);
|
||||
for (size_t i = 0; i < domains.size(); i++) {
|
||||
const std::string &domain_name = domains[i];
|
||||
const std::string filename = domain_name + ".mo";
|
||||
Catalog catalog;
|
||||
for (const std::string &path : catalog_paths) {
|
||||
if (load_file(path + "/" + filename, catalog)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catalogs_.append(std::move(catalog));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<StringRefNull> translate(const int domain,
|
||||
const StringRef context,
|
||||
const StringRef str) const
|
||||
{
|
||||
if (domain < 0 || domain >= catalogs_.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const MessageKeyRef key{context, str};
|
||||
const std::string *result = catalogs_[domain].lookup_ptr_as(key);
|
||||
if (!result) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
|
||||
const std::string &error()
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<std::string> get_catalog_paths(const Info &info, const Vector<std::string> &paths)
|
||||
{
|
||||
/* Find language folders. */
|
||||
Vector<std::string> lang_folders;
|
||||
if (info.language.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Blender uses non-standard uppercase script zh_HANS instead of zh_Hans, try both. */
|
||||
Vector<std::string> scripts = {info.script};
|
||||
if (!info.script.empty()) {
|
||||
std::string script_uppercase = info.script;
|
||||
for (char &c : script_uppercase) {
|
||||
make_upper_ascii(c);
|
||||
}
|
||||
scripts.append(script_uppercase);
|
||||
}
|
||||
|
||||
for (const std::string &script : scripts) {
|
||||
std::string language = info.language;
|
||||
if (!script.empty()) {
|
||||
language += "_" + script;
|
||||
}
|
||||
if (!info.variant.empty() && !info.country.empty()) {
|
||||
lang_folders.append(language + "_" + info.country + "@" + info.variant);
|
||||
}
|
||||
if (!info.variant.empty()) {
|
||||
lang_folders.append(language + "@" + info.variant);
|
||||
}
|
||||
if (!info.country.empty()) {
|
||||
lang_folders.append(language + "_" + info.country);
|
||||
}
|
||||
lang_folders.append(language);
|
||||
}
|
||||
|
||||
/* Find catalogs in language folders. */
|
||||
Vector<std::string> result;
|
||||
result.reserve(lang_folders.size() * paths.size());
|
||||
for (const std::string &lang_folder : lang_folders) {
|
||||
for (const std::string &search_path : paths) {
|
||||
result.append(search_path + "/" + lang_folder + "/LC_MESSAGES");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool load_file(const std::string &filepath, Catalog &catalog)
|
||||
{
|
||||
MOFile mo(filepath);
|
||||
if (!mo.error().empty()) {
|
||||
error_ = mo.error();
|
||||
return false;
|
||||
}
|
||||
if (mo.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only support UTF8 encoded files, as created by our msgfmt tool. */
|
||||
const std::string mo_encoding = extract(mo.value(0), "charset=", " \r\n;");
|
||||
if (mo_encoding.empty()) {
|
||||
error_ = "Invalid mo-format, encoding is not specified";
|
||||
return false;
|
||||
}
|
||||
if (mo_encoding != "UTF-8") {
|
||||
error_ = "supported mo-format, encoding must be UTF-8";
|
||||
return false;
|
||||
}
|
||||
|
||||
CLOG_INFO(&LOG, "Load messages from \"%s\"", filepath.c_str());
|
||||
|
||||
/* Create context + key to translated string mapping. */
|
||||
for (size_t i = 0; i < mo.size(); i++) {
|
||||
const MessageKey key(mo.key(i));
|
||||
catalog.add(std::move(key), std::string(mo.value(i)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string extract(StringRef meta, const std::string &key, const StringRef separators)
|
||||
{
|
||||
const size_t pos = meta.find(key);
|
||||
if (pos == StringRef::not_found) {
|
||||
return "";
|
||||
}
|
||||
meta = meta.substr(pos + key.size());
|
||||
const size_t end_pos = meta.find_first_of(separators);
|
||||
return std::string(meta.substr(0, end_pos));
|
||||
}
|
||||
};
|
||||
|
||||
/* Public API */
|
||||
|
||||
/* Lazily init inside function so it gets destructed before guardedalloc leak check. */
|
||||
static std::unique_ptr<MOMessages> &global_messages()
|
||||
{
|
||||
static std::unique_ptr<MOMessages> global_messages_;
|
||||
return global_messages_;
|
||||
}
|
||||
|
||||
static std::string global_full_name;
|
||||
|
||||
void init(const StringRef locale_full_name,
|
||||
const Vector<std::string> &domains,
|
||||
const Vector<std::string> &paths)
|
||||
{
|
||||
Info info(locale_full_name);
|
||||
if (global_full_name == info.to_full_name()) {
|
||||
return;
|
||||
}
|
||||
|
||||
global_messages() = std::make_unique<MOMessages>(info, domains, paths);
|
||||
global_full_name = info.to_full_name();
|
||||
|
||||
if (global_messages()->error().empty()) {
|
||||
CLOG_INFO(&LOG, "Locale %s used for translation", global_full_name.c_str());
|
||||
}
|
||||
else {
|
||||
CLOG_ERROR(
|
||||
&LOG, "Locale %s: %s", global_full_name.c_str(), global_messages()->error().c_str());
|
||||
free();
|
||||
}
|
||||
}
|
||||
|
||||
void free()
|
||||
{
|
||||
global_messages().reset();
|
||||
global_full_name = "";
|
||||
}
|
||||
|
||||
std::optional<StringRefNull> translate(const int domain,
|
||||
const StringRef context,
|
||||
const StringRef key)
|
||||
{
|
||||
if (!global_messages()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return global_messages()->translate(domain, context, key);
|
||||
}
|
||||
|
||||
const char *full_name()
|
||||
{
|
||||
return global_full_name.c_str();
|
||||
}
|
||||
|
||||
} // namespace blender::locale
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: BSL-1.0 */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*
|
||||
* Adapted from `boost::locale`.
|
||||
*/
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender::locale {
|
||||
|
||||
void init(const StringRef locale_full_name, /* Local name. */
|
||||
const Vector<std::string> &domains, /* Application names. */
|
||||
const Vector<std::string> &paths); /* Search paths for .mo files. */
|
||||
void free();
|
||||
|
||||
std::optional<StringRefNull> translate(int domain, StringRef context, StringRef key);
|
||||
const char *full_name();
|
||||
|
||||
#if defined(__APPLE__) && !defined(WITH_HEADLESS) && !defined(WITH_GHOST_SDL)
|
||||
std::string macos_user_locale();
|
||||
#endif
|
||||
|
||||
} // namespace blender::locale
|
||||
@@ -0,0 +1,46 @@
|
||||
/* SPDX-FileCopyrightText: 2012 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blt
|
||||
*/
|
||||
|
||||
#include "messages.hh"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
namespace blender::locale {
|
||||
|
||||
#if !defined(WITH_HEADLESS) && !defined(WITH_GHOST_SDL)
|
||||
/* Get current locale. */
|
||||
std::string macos_user_locale()
|
||||
{
|
||||
std::string result;
|
||||
|
||||
@autoreleasepool {
|
||||
CFLocaleRef myCFLocale = CFLocaleCopyCurrent();
|
||||
NSLocale *myNSLocale = (NSLocale *)myCFLocale;
|
||||
[myNSLocale autorelease];
|
||||
|
||||
/* This produces gettext-invalid locale in recent macOS versions (11.4),
|
||||
* like `ko-Kore_KR` instead of `ko_KR`. See #88877. */
|
||||
// NSString *nsIdentifier = [myNSLocale localeIdentifier];
|
||||
|
||||
NSString *nsIdentifier = myNSLocale.languageCode;
|
||||
NSString *nsIdentifier_country = myNSLocale.countryCode;
|
||||
if (nsIdentifier.length != 0 && nsIdentifier_country.length != 0) {
|
||||
nsIdentifier = [NSString stringWithFormat:@"%@_%@", nsIdentifier, nsIdentifier_country];
|
||||
}
|
||||
|
||||
result = nsIdentifier.UTF8String;
|
||||
}
|
||||
|
||||
return result + ".UTF-8";
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace blender::locale
|
||||
@@ -0,0 +1,43 @@
|
||||
# SPDX-FileCopyrightText: 2017 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build `msgfmt` executable.
|
||||
|
||||
set(INC
|
||||
)
|
||||
|
||||
set(SRC
|
||||
msgfmt.cc
|
||||
)
|
||||
|
||||
# Manifest required for selecting comctl32 v6 required by the crash popup.
|
||||
if(WIN32 AND NOT WITH_WINDOWS_EXTERNAL_MANIFEST)
|
||||
LIST(APPEND SRC "${CMAKE_BINARY_DIR}/tests.exe.manifest")
|
||||
endif()
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::dependencies::zlib
|
||||
${PLATFORM_LINKLIBS})
|
||||
|
||||
add_c_and_cxx_flags_custom_test(msgfmt)
|
||||
|
||||
if(WIN32)
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG " /nodefaultlib:MSVCRT.lib")
|
||||
list(APPEND LIB bf_intern_utfconv)
|
||||
endif()
|
||||
|
||||
add_executable(msgfmt ${SRC})
|
||||
setup_platform_linker_flags(msgfmt)
|
||||
blender_target_include_dirs(msgfmt ${INC})
|
||||
target_link_libraries(msgfmt ${LIB})
|
||||
|
||||
# If using an external manifest, copy it to the output directory after building.
|
||||
if(WITH_WINDOWS_EXTERNAL_MANIFEST)
|
||||
add_custom_command(TARGET msgfmt POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/tests.exe.manifest $<TARGET_FILE_DIR:msgfmt>/$<TARGET_FILE_NAME:msgfmt>.manifest
|
||||
)
|
||||
endif()
|
||||
400
blender-5.2.0/source/blender/blentranslation/msgfmt/msgfmt.cc
Normal file
400
blender-5.2.0/source/blender/blentranslation/msgfmt/msgfmt.cc
Normal file
@@ -0,0 +1,400 @@
|
||||
/* SPDX-FileCopyrightText: 2017 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/*
|
||||
* Based on C++ version by `Sergey Sharybin <sergey.vfx@gmail.com>`.
|
||||
* Based on Python script `msgfmt.py` from Python source code tree, which was written by
|
||||
* `Martin v. Löwis <loewis@informatik.hu-berlin.de>`.
|
||||
*
|
||||
* Generate binary message catalog from textual translation description.
|
||||
*
|
||||
* This program converts a textual Uniform-style message catalog (.po file)
|
||||
* into a binary GNU catalog (.mo file).
|
||||
* This is essentially the same function as the GNU msgfmt program,
|
||||
* however, it is a simpler implementation.
|
||||
*
|
||||
* Usage: msgfmt input.po output.po
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* Stupid stub necessary because some BLI files includes winstuff.h, which uses G a bit... */
|
||||
#ifdef WIN32
|
||||
struct Global {
|
||||
void *dummy;
|
||||
};
|
||||
|
||||
Global G;
|
||||
#endif
|
||||
|
||||
enum eSectionType {
|
||||
SECTION_NONE = 0,
|
||||
SECTION_CTX = 1,
|
||||
SECTION_ID = 2,
|
||||
SECTION_STR = 3,
|
||||
};
|
||||
|
||||
struct Message {
|
||||
std::string ctxt;
|
||||
std::string id;
|
||||
std::string str;
|
||||
|
||||
bool is_fuzzy = false;
|
||||
};
|
||||
|
||||
static StringRef unescape(std::string &str)
|
||||
{
|
||||
int curr, next;
|
||||
for (curr = next = 0; next < str.size(); curr++, next++) {
|
||||
if (str[next] == '\\') {
|
||||
/* Get rid of trailing escape char. */
|
||||
if (next == str.size() - 1) {
|
||||
curr--;
|
||||
continue;
|
||||
}
|
||||
switch (str[next + 1]) {
|
||||
case '\\':
|
||||
str[curr] = '\\';
|
||||
next++;
|
||||
break;
|
||||
case 'n':
|
||||
str[curr] = '\n';
|
||||
next++;
|
||||
break;
|
||||
case 't':
|
||||
str[curr] = '\t';
|
||||
next++;
|
||||
break;
|
||||
default:
|
||||
/* Get rid of useless escape char. */
|
||||
next++;
|
||||
str[curr] = str[next];
|
||||
}
|
||||
}
|
||||
else if (curr != next) {
|
||||
str[curr] = str[next];
|
||||
}
|
||||
}
|
||||
StringRef ret_str = str;
|
||||
BLI_assert(curr <= str.size());
|
||||
|
||||
if (ret_str[0] == '"' && ret_str[curr - 1] == '"') {
|
||||
return ret_str.substr(1, curr - 2);
|
||||
}
|
||||
return ret_str.substr(0, curr);
|
||||
}
|
||||
|
||||
BLI_INLINE size_t uint32_to_bytes(const int value, char *bytes)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < sizeof(value); i++) {
|
||||
bytes[i] = char((value >> (int(i) * 8)) & 0xff);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
BLI_INLINE size_t msg_to_bytes(const std::string &msg, char *bytes, uint32_t size)
|
||||
{
|
||||
BLI_assert(msg.size() == size - 1);
|
||||
memcpy(bytes, msg.c_str(), size);
|
||||
return size;
|
||||
}
|
||||
|
||||
struct Offset {
|
||||
uint32_t key_offset, key_len, val_offset, val_len;
|
||||
};
|
||||
|
||||
/* Return the generated binary output. */
|
||||
static char *generate(Map<std::string, std::string> &messages, size_t *r_output_size)
|
||||
{
|
||||
using MapItem = Map<std::string, std::string>::MutableItem;
|
||||
struct Item {
|
||||
StringRef key;
|
||||
StringRef value;
|
||||
|
||||
Item(const MapItem &other) : key(other.key), value(other.value) {}
|
||||
Item(const Item &other) = default;
|
||||
Item &operator=(const Item &other) = default;
|
||||
};
|
||||
const uint32_t num_keys = messages.size();
|
||||
|
||||
/* Get a vector of (key, value) pairs sorted by their keys. */
|
||||
Vector<Item> items = {};
|
||||
for (const auto message_items_iter : messages.items()) {
|
||||
items.append(Item(message_items_iter));
|
||||
}
|
||||
std::ranges::sort(items, [](const Item &a, const Item &b) -> bool { return a.key < b.key; });
|
||||
|
||||
Offset *offsets = MEM_new_array_zeroed<Offset>(num_keys, __func__);
|
||||
uint32_t tot_keys_len = 0;
|
||||
uint32_t tot_vals_len = 0;
|
||||
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
Offset &off = offsets[i];
|
||||
|
||||
/* For each string, we need size and file offset.
|
||||
* Each string is null terminated; the null does not count into the size. */
|
||||
off.key_offset = tot_keys_len;
|
||||
off.key_len = uint32_t(items[i].key.size());
|
||||
tot_keys_len += off.key_len + 1;
|
||||
|
||||
off.val_offset = tot_vals_len;
|
||||
off.val_len = uint32_t(items[i].value.size());
|
||||
tot_vals_len += off.val_len + 1;
|
||||
}
|
||||
|
||||
/* The header is 7 32-bit unsigned integers.
|
||||
* Then comes the keys index table, then the values index table. */
|
||||
const uint32_t idx_keystart = 7 * 4;
|
||||
const uint32_t idx_valstart = idx_keystart + 8 * num_keys;
|
||||
/* We don't use hash tables, so the keys start right after the index tables. */
|
||||
const uint32_t keystart = idx_valstart + 8 * num_keys;
|
||||
/* and the values start after the keys */
|
||||
const uint32_t valstart = keystart + tot_keys_len;
|
||||
|
||||
/* Final buffer representing the binary MO file. */
|
||||
*r_output_size = valstart + tot_vals_len;
|
||||
char *output = MEM_new_array_zeroed<char>(*r_output_size, __func__);
|
||||
char *h = output;
|
||||
char *ik = output + idx_keystart;
|
||||
char *iv = output + idx_valstart;
|
||||
char *k = output + keystart;
|
||||
char *v = output + valstart;
|
||||
|
||||
h += uint32_to_bytes(0x950412de, h); /* Magic */
|
||||
h += uint32_to_bytes(0x0, h); /* Version */
|
||||
h += uint32_to_bytes(num_keys, h); /* Number of entries */
|
||||
h += uint32_to_bytes(idx_keystart, h); /* Start of key index */
|
||||
h += uint32_to_bytes(idx_valstart, h); /* Start of value index */
|
||||
h += uint32_to_bytes(0, h); /* Size of hash table */
|
||||
h += uint32_to_bytes(0, h); /* Offset of hash table */
|
||||
|
||||
BLI_assert(h == ik);
|
||||
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
const Offset &off = offsets[i];
|
||||
|
||||
/* The index table first has the list of keys, then the list of values.
|
||||
* Each entry has first the size of the string, then the file offset. */
|
||||
ik += uint32_to_bytes(off.key_len, ik);
|
||||
ik += uint32_to_bytes(off.key_offset + keystart, ik);
|
||||
iv += uint32_to_bytes(off.val_len, iv);
|
||||
iv += uint32_to_bytes(off.val_offset + valstart, iv);
|
||||
|
||||
k += msg_to_bytes(items[i].key, k, off.key_len + 1);
|
||||
v += msg_to_bytes(items[i].value, v, off.val_len + 1);
|
||||
}
|
||||
|
||||
BLI_assert(ik == output + idx_valstart);
|
||||
BLI_assert(iv == output + keystart);
|
||||
BLI_assert(k == output + valstart);
|
||||
|
||||
MEM_delete(offsets);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static void clear(Message &msg)
|
||||
{
|
||||
msg.ctxt.clear();
|
||||
msg.id.clear();
|
||||
msg.str.clear();
|
||||
msg.is_fuzzy = false;
|
||||
}
|
||||
|
||||
/* Add a non-fuzzy translation to the dictionary. */
|
||||
static void add(Map<std::string, std::string> &messages, Message &msg)
|
||||
{
|
||||
if (!msg.is_fuzzy && !msg.str.empty()) {
|
||||
std::string msgkey;
|
||||
if (msg.ctxt.empty()) {
|
||||
msgkey = std::move(msg.id);
|
||||
}
|
||||
else {
|
||||
/* '\x04' is the context/msgid separator. */
|
||||
msgkey = msg.ctxt + "\x04" + msg.id;
|
||||
}
|
||||
|
||||
messages.add(std::move(msgkey), std::move(msg.str));
|
||||
}
|
||||
clear(msg);
|
||||
}
|
||||
|
||||
static int make(const char *input_file_name, const char *output_file_name)
|
||||
{
|
||||
Map<std::string, std::string> messages;
|
||||
|
||||
const char *msgctxt_kw = "msgctxt";
|
||||
const char *msgid_kw = "msgid";
|
||||
const char *msgid_plural_kw = "msgid_plural";
|
||||
const char *msgstr_kw = "msgstr";
|
||||
const size_t msgctxt_len = strlen(msgctxt_kw);
|
||||
const size_t msgid_len = strlen(msgid_kw);
|
||||
const size_t msgid_plural_len = strlen(msgid_plural_kw);
|
||||
const size_t msgstr_len = strlen(msgstr_kw);
|
||||
|
||||
/* NOTE: For now, we assume file encoding is always UTF8. */
|
||||
|
||||
eSectionType section = SECTION_NONE;
|
||||
bool is_plural = false;
|
||||
|
||||
Message msg{};
|
||||
|
||||
LinkNode *input_file_lines = BLI_file_read_as_lines(input_file_name);
|
||||
LinkNode *ifl = input_file_lines;
|
||||
|
||||
/* Parse the catalog. */
|
||||
for (int lno = 1; ifl; ifl = ifl->next, lno++) {
|
||||
std::string line = static_cast<char *>(ifl->link);
|
||||
StringRef l = line;
|
||||
if (l.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
const bool is_comment = (l[0] == '#');
|
||||
/* If we get a comment line after a msgstr, this is a new entry. */
|
||||
if (is_comment) {
|
||||
if (section == SECTION_STR) {
|
||||
add(messages, msg);
|
||||
section = SECTION_NONE;
|
||||
}
|
||||
/* Record a fuzzy mark. */
|
||||
if (l[1] == ',' && l.find("fuzzy") != StringRef::not_found) {
|
||||
msg.is_fuzzy = true;
|
||||
}
|
||||
/* Skip comments */
|
||||
continue;
|
||||
}
|
||||
if (l.startswith(msgctxt_kw)) {
|
||||
if (section == SECTION_STR) {
|
||||
/* New message, output previous section. */
|
||||
add(messages, msg);
|
||||
}
|
||||
if (!ELEM(section, SECTION_NONE, SECTION_STR)) {
|
||||
printf("msgctxt not at start of new message on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
section = SECTION_CTX;
|
||||
l = l.substr(msgctxt_len);
|
||||
clear(msg);
|
||||
}
|
||||
else if (l.startswith(msgid_plural_kw)) {
|
||||
/* This is a message with plural forms. */
|
||||
if (section != SECTION_ID) {
|
||||
printf("msgid_plural not preceded by msgid on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
l = l.substr(msgid_plural_len);
|
||||
msg.id += "\0"; /* separator of singular and plural */
|
||||
is_plural = true;
|
||||
}
|
||||
else if (l.startswith(msgid_kw)) {
|
||||
if (section == SECTION_STR) {
|
||||
add(messages, msg);
|
||||
}
|
||||
if (section != SECTION_CTX) {
|
||||
clear(msg);
|
||||
}
|
||||
section = SECTION_ID;
|
||||
l = l.substr(msgid_len);
|
||||
is_plural = false;
|
||||
}
|
||||
else if (l.startswith(msgstr_kw)) {
|
||||
l = l.substr(msgstr_len);
|
||||
/* Now we are in a `msgstr` section. */
|
||||
section = SECTION_STR;
|
||||
if (l[0] == '[') {
|
||||
if (!is_plural) {
|
||||
printf("plural without msgid_plural on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
int64_t close_bracket_idx = l.find(']');
|
||||
if (close_bracket_idx == StringRef::not_found) {
|
||||
printf("Syntax error on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
l = l.substr(close_bracket_idx + 1);
|
||||
if (!msg.str.empty()) {
|
||||
msg.str += "\0"; /* Separator of the various plural forms. */
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (is_plural) {
|
||||
printf("indexed msgstr required for plural on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Skip empty lines. */
|
||||
l = l.trim();
|
||||
if (l.is_empty()) {
|
||||
if (section == SECTION_STR) {
|
||||
add(messages, msg);
|
||||
}
|
||||
section = SECTION_NONE;
|
||||
continue;
|
||||
}
|
||||
line = l;
|
||||
l = unescape(line);
|
||||
if (section == SECTION_CTX) {
|
||||
msg.ctxt += l;
|
||||
}
|
||||
else if (section == SECTION_ID) {
|
||||
msg.id += l;
|
||||
}
|
||||
else if (section == SECTION_STR) {
|
||||
msg.str += l;
|
||||
}
|
||||
else {
|
||||
printf("Syntax error on %s:%d\n", input_file_name, lno);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
/* Add last entry */
|
||||
if (section == SECTION_STR) {
|
||||
add(messages, msg);
|
||||
}
|
||||
|
||||
BLI_file_free_lines(input_file_lines);
|
||||
|
||||
/* Compute output */
|
||||
size_t output_size;
|
||||
char *output = generate(messages, &output_size);
|
||||
|
||||
FILE *fp = BLI_fopen(output_file_name, "wb");
|
||||
fwrite(output, 1, output_size, fp);
|
||||
fclose(fp);
|
||||
|
||||
MEM_delete(output);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3) {
|
||||
printf("Usage: %s <input.po> <output.mo>\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const char *input_file = argv[1];
|
||||
const char *output_file = argv[2];
|
||||
|
||||
return blender::make(input_file, output_file);
|
||||
}
|
||||
Reference in New Issue
Block a user