6.4 KiB
Writing Localizeable Code
As part of Microsoft's commitment to global-ready products, our developer tools are localized into 14 different languages. For this effort, vcpkg has hooks that allow our localization team to translate all messages shown to the user.
All localized messages start in include/vcpkg/base/message-data.inc.h
. After modifying those messages, you will need to regenerate locales/messages.json
using vcpkg x-generate-default-message-map
and include the updated file in your PR. After merging the PR, the localization team will modify all the language-specific files under locales/
.
Creating a New Message
Messages are defined by calls to DECLARE_MESSAGE
include/vcpkg/base/message-data.inc.h
.
DECLARE_MESSAGE(<message-name>, <parameters>, <comment>, <english-message>)
For example:
DECLARE_MESSAGE(AddVersionVersionAlreadyInFile, (msg::version, msg::path), "", "version {version} is already in {path}")
The messages are sorted alphabetically by <message-name>
. The <message-name>
should be descriptive, unique, and CamelCase
.
Parameters
If the message contains placeholders, each one must have a corresponding parameter.
For example, "Installing {package_name}:{triplet}"
should have the parameter list (msg::package_name, msg::triplet)
.
This allows us to make sure that we don't have format errors, where one forgets a placeholder,
or adds an extra one.
Each parameter in the list should be used in the message.
Parameters are defined in include/vcpkg/base/message-args.inc.h
.
Comment
The comment is used to give context to the translator as to what a placeholder will be replaced with.
For example, in BuildResultSummaryHeader
("SUMMARY FOR {triplet}"
),
it will note that an example of {triplet}
is 'x64-windows'
.
Most message placeholders (see messages.h
for the full list - search for DECLARE_MSG_ARG
) have comments associated with them already.
Only general placeholders like {value}
, {expected}
, and {actual}
don't have comments associated with them.
If you use any of these placeholders, write example of {<name>} is '<expected-value>'
for each of those placeholders,
separated by \n
. You can also add context if you feel it's necessary as the first line.
Message
Messages in vcpkg are written in American English. They should not contain:
- formatting:
- indentation should be added with the
append_indent()
function; if you need to add more than one indentation, you can useappend_indent(N)
- Any other interesting characters (like
-
for lists, for example) should useappend_raw(...)
- indentation should be added with the
- or for the prefixes:
"warning: "
, instead usemsg::format(msgWarningMessage).append(msgMyWarning)
"error: "
, instead usemsgErrorMessage
"internal error: "
, instead usemsgInternalErrorMessage
.
They should also not be simple, locale-invariant messages -- something like, for example,
{file}:{line}:{column}:
should be done with LocalizedString::from_raw(fmt::format("{}:{}:{}", file, line, column))
.
Using Messages
For each message MyMessage
, include/base/messages.h
will declare a ::vcpkg::msgMyMessage
object of some MessageT
type. Many functions accept MessageT
objects as arguments, however the primary method is msg::format()
:
LocalizedString make_my_info(const Path& p) {
return msg::format(msgMyMessage, msg::path = p);
}
Declaring Message-Forwarding Functions
There are three macros defined by include/base/messages.h
that aid in creating perfect message forwarders: VCPKG_DECL_MSG_TEMPLATE
, VCPKG_DECL_MSG_ARGS
, and VCPKG_EXPAND_MSG_ARGS
. These work similarly to class...Args
, Args&&...args
, and std::forward<Args>(args)...
respectively.
For example:
template<VCPKG_DECL_MSG_TEMPLATE>
void add_info_message(VCPKG_DECL_MSG_ARGS) {
m_info.append(VCPKG_EXPAND_MSG_ARGS).append_raw('\n');
}
We discourage creating new Message-forwarding functions and encourage accepting a LocalizedString
instead.
Example
Let's create a vcpkg hello world command to show off how one can use the messages API.
// include/vcpkg/base/message-data.inc.h
// note that we add additional context in the comment here
DECLARE_MESSAGE(World, (), "We will say hello to 'world' if no name is given", "world")
// here, `{value}` is a placeholder that doesn't have example text, so we need to give it ourselves
DECLARE_MESSAGE(Hello, (msg::value), "example for {value} is 'world'", "Hello, {value}!")
// here, `{triplet}` _already has_ example text, so it's fine to not give a comment
DECLARE_MESSAGE(MyTripletIs, (msg::triplet), "", "My triplet is {triplet}.")
// commands.hello-world.cpp
#include <vcpkg/base/messages.h>
#include <vcpkg/commands.hello-world.h>
#include <vcpkg/vcpkgcmdarguments.h>
#include <vcpkg/vcpkgpaths.h>
namespace
{
const CommandStructure HELLO_WORLD_COMMAND_STRUCTURE = {
create_example_string("hello-world <name>"),
0,
1,
{{}, {}, {}},
nullptr,
};
}
namespace vcpkg
{
void HelloWorldCommand::perform_and_exit(const VcpkgCmdArguments& args,
const VcpkgPaths& paths
Triplet default_triplet,
Triplet) const
{
(void)args.parse_arguments(HELLO_WORLD_COMMAND_STRUCTURE);
LocalizedString name;
if (args.command_arguments.size() == 0)
{
name = msg::format(msgWorld);
}
else
{
name = LocalizedString::from_raw(args.command_arguments[0]);
}
msg::println(msgHello, msg::value = name);
msg::println(msgMyTripletIs, msg::triplet = default_triplet);
}
}
Selecting a Language
vcpkg chooses a language through the VSLANG
environment variable, which should be set to a valid LCID (locale identifier, 4-byte value representing a language). If VSLANG
is not set or is set to an invalid LCID, we default to English. That said, if you wish to change the language, then set VSLANG
to any of the following LCIDs:
1029: Czech
1031: German
1033: English
3082: Spanish (Spain)
1036: French
1040: Italian
1041: Japanese
1042: Korean
1045: Polish
1046: Portuguese (Brazil)
1049: Russian
1055: Turkish
2052: Chinese (Simplified)
1028: Chinese (Traditional)