In the old days[1], when using Windows resources, people have been using:
// in your project_strings.h file
#define STRING_PASSWORD 1
...
// resources project.rc
#include "project_strings.h"
STRINGTABLE
BEGIN
STRING_PASSWORD "Password:"
...
END
// in some other file
#include "project_strings.h"
CString str(STRING_PASSWORD);
The CString knew about windows resources (ugly dependency) and could go and read the string password. The #define is definitively very ugly in modern C++, but resources would not understand a static const variable or an inline function.
The easiest way to replicate this in a somewhat similar way is to use a header file with string declarations and then reference those strings anywhere you need them.
// in your project_strings.h
namespace MyProjectStrings {
const char *password;
...
}
// the project_strings.cpp for the strings
#include "project_strings.h"
namespace MyProjectStrings {
const char *password = "Password:";
...
}
// some random user who needs that string
#include "project_strings.h"
std::string password(MyProjectStrings::password);
Now all your strings are in project_strings.cpp and you cannot as easily translate them with tr()... but you could transform all those strings declarations with functions:
// in your project_strings.h
namespace MyProjectStrings {
const char *password(); //[2]
...
}
// the project_strings.cpp for the strings
#include "project_strings.h"
namespace MyProjectStrings {
const char *password() { return QObject::tr("Password:"); }
...
}
// some random user who needs that string
#include "project_strings.h"
std::string password(MyProjectStrings::password()); //[3]
And Voilà! You have a single long table of all your strings in one place and translatable.
[1] Many people still use that scheme!
[2] The function could return std::string to 100% prevent modifying the original.
[3] In this last example the string reference uses () since it's a function call.