Michael R Sweet
Copyright © 2022-2024 by Michael R Sweet
StringsUtil provides a library for using Apple ".strings" localization files and a utility for managing those files. It is intended as a free, smaller, embeddable, and more flexible alternative to GNU gettext. Key features include:
Support for localizing using both Apple ".strings" and GNU gettext ".po" files.
Simple C/C++ library with support for embedding localization data in an executable and/or loading localizations from external files.
Tools for exporting, importing, and merging localization files.
Tool for reporting on the quality of a localization.
Tool for scanning C/C++ source files for localization strings.
Tool for doing a first pass machine translation using the LibreTranslate service/software.
Apple ".strings" files are localization files used on macOS and iOS, as well as for localizing Internet Printing Protocol (IPP) attributes and values. The format consists of lines containing key/text pairs:
"key string" = "localized text value";
C-style comments can be included before a pair to provide information to a localizer:
/* A whitty comment for the localizer about this string */
"key string" = "localized text value";
Each ".strings" file represents a single language or locale.
Most open source software uses the GNU gettext library which supports a different ".po" file format:
# A whitty comment for the localizer about this string
msgid "key string"
msgstr "localized text value"
This format is "compiled" into binary ".mo" files that are typically stored in a system directory. Like ".strings" files, one ".po" file is used for every language or locale.
StringUtils supports importing and exporting ".po" files, when needed, but uses the Apple ".strings" format exclusively both on disk and in memory.
stringsutil
ToolThe stringsutil
tool allows you to manage your ".strings" files. Create a ".strings" file by scanning source files in the current directory with the "scan" sub-command:
stringsutil -f base.strings scan *.[ch]
Create a ".po" file for external localizers to work with using the "export" sub-command:
stringsutil -f base.strings export es.po
When the localizer is done, use the "import" sub-command to import the strings from the ".po" file:
cp base.strings es.strings
stringsutil -f es.strings import es.po
Run the "report" sub-command to see how well the localizer did:
stringsutil -f base.strings report es.strings
When you have made source changes that affect the localization strings, use the "scan" sub-command again to update the base strings:
stringsutil -f base.strings scan *.[ch]
Then add those changes to the "es.strings" file with the "merge" sub-command:
stringsutil -f es.strings -c merge base.strings
The "translate" sub-command uses a LibreTranslate service to do a first-pass machine translation of your strings. For example, the following command will use a local Docker instance of LibreTranslate:
stringsutil -f es.strings -l es -T http://localhost:5000 translate base.strings
You also use the "export" command to produce a C header file containing a strings file that can be embedded in a program:
stringsutil -f es.strings export es_strings.h
libsf
LibraryThe libsf
library has a single header file:
#include <sf.h>
Use the pkg-config
command to get the proper compiler and linker options:
CFLAGS = `pkg-config --cflags libsf`
LIBS = `pkg-config --libs libsf`
cc -o myprogram `pkg-config --cflags libsf` myprogram.c `pkg-config --libs libsf`
A typical program will either have a directory containing ".strings" files installed on disk or a collection of header files containing ".strings" files as constant C strings. Call sfSetLocale
to initialize the current locale and then sfRegisterDirectory
or sfRegisterString
to initialize the default localization strings:
// ".strings" files in "/usr/local/share/myapp/strings"...
#include <sf.h>
...
sfSetLocale();
sfRegisterDirectory("/usr/local/share/myapp/strings");
// ".strings" files in constant strings...
#include <sf.h>
#include "de_strings.h"
#include "es_strings.h"
#include "fr_strings.h"
#include "it_strings.h"
#include "ja_strings.h"
...
sfSetLocale();
sfRegisterString("de", de_strings);
sfRegisterString("es", es_strings);
sfRegisterString("fr", fr_strings);
sfRegisterString("it", it_strings);
sfRegisterString("ja", ja_strings);
Once the current local is initialized, you can use the sfPrintf
and sfPuts
functions to display localized messages. The SFSTR
macro is provided by the <sf.h>
header to identify strings for localization:
sfPuts(stdout, SFSTR("Usage: myprogram [OPTIONS] FILENAME"));
...
sfPrintf(stderr, SFSTR("myprogram: Expected '-n' value %d out of range."), n);
...
sfPrintf(stderr, SFSTR("myprogram: Syntax error on line %d of '%s'."), linenum, filename);
Add a localization string.
bool sfAddString(sf_t *sf, const char *key, const char *text, const char *comment);
sf | Strings |
---|---|
key | Key |
text | Text |
comment | Comment or NULL for none |
true
on success, false
on error
This function adds a localization string to the collection.
Free a collection of localization strings.
void sfDelete(sf_t *sf);
sf | Localization strings |
---|
This function frees all memory associated with the localization strings.
Format a localized string.
const char *sfFormatString(sf_t *sf, char *buffer, size_t bufsize, const char *key, ...);
sf | Localization strings or NULL for default |
---|---|
buffer | Output buffer |
bufsize | Size of output buffer |
key | Printf-style format/key string |
... | Additional arguments as needed |
Formatted localized string
This function formats a printf-style localized string using the specified
localization strings. If no localization exists for the format (key) string,
the original string is used. All snprintf
format specifiers are supported.
The default localization strings ("sf" passed as NULL
) are initialized
using the sfSetLocale
, sfRegisterDirectory
, and
sfRegisterString
functions.
Get the last error message, if any.
const char *sfGetError(sf_t *sf);
sf | Localization strings |
---|
Last error message or NULL
for none
This function returns the last error from sfLoadFile
and
sfLoadString
, if any.
Lookup a localized string.
const char *sfGetString(sf_t *sf, const char *key);
sf | Localization strings or NULL for the default |
---|---|
key | Key string |
Localized string
This function looks up a localized string for the specified key string.
If no localization exists, the key string is returned.
The default localization strings ("sf" passed as NULL
) are initialized
using the sfSetLocale
, sfRegisterDirectory
, and
sfRegisterString
functions.
Determine whether a string is localized.
bool sfHasString(sf_t *sf, const char *key);
sf | Localization strings |
---|---|
key | Key string |
true
if the string exists, false
otherwise
This function looks up a localization string, returning true
if the string
exists and false
otherwise.
Load a ".strings" file.
bool sfLoadFile(sf_t *sf, const char *filename);
sf | Localization strings |
---|---|
filename | File to load |
true
on success, false
on failure
This function loads a ".strings" file. The "sf" argument specifies a
collection of localization strings that was created using the sfNew
function.
When loading the strings, any existing strings in the collection are left
unchanged.
Load a ".strings" file from a compiled-in string.
bool sfLoadString(sf_t *sf, const char *data);
sf | Localization strings |
---|---|
data | Data to load |
true
on success, false
on failure
This function loads a ".strings" file from a compiled-in string. The "sf"
argument specifies a collection of localization strings that was created
using the sfNew
function.
When loading the strings, any existing strings in the collection are left
unchanged.
Create a new (empty) set of localization strings.
sf_t *sfNew(void);
Localization strings
This function creates a new (empty) set of localization strings. Use the
sfLoadFile
and/or sfLoadString
functions to load
localization strings.
Print a formatted localized message followed by a newline.
void sfPrintf(FILE *fp, const char *message, ...);
fp | Output file |
---|---|
message | Printf-style message |
... | Additional arguments as needed |
This function prints a formatted localized message followed by a newline to
the specified file, typically stdout
or stderr
. You must call
sfSetLocale
and sfRegisterString
or sfRegisterDirectory
to initialize the message catalog that is used.
Print a localized message followed by a newline.
void sfPuts(FILE *fp, const char *message);
fp | Output file |
---|---|
message | Message |
This function prints a localized message followed by a newline to the
specified file, typically stdout
or stderr
. You must call
sfSetLocale
and sfRegisterString
or sfRegisterDirectory
to initialize the message catalog that is used.
Register ".strings" files in a directory.
void sfRegisterDirectory(const char *directory);
directory | Directory of .strings files |
---|
This function registers ".strings" files in a directory. You must call
sfSetLocale
first to initialize the current locale.
Register a ".strings" file from a compiled-in string.
void sfRegisterString(const char *locale, const char *data);
locale | Locale |
---|---|
data | Strings data |
This function registers a ".strings" file from a compiled-in string. You
must call sfSetLocale
first to initialize the current locale.
Remove a localization string.
bool sfRemoveString(sf_t *sf, const char *key);
sf | Strings |
---|---|
key | Key |
'true on success,
false on error
This function removes a localization string from the collection.
Set the current locale.
void sfSetLocale(void);
This function calls setlocale
to initialize the current locale based on
the current user session, and then creates an empty message catalog that is
filled by calls to sfRegisterDirectory
and/or sfRegisterString
.
Strings file
typedef struct _sf_s sf_t;