Elements not initialized anywhere will be NULL, no "", and using NULL with a function like printf is undefined, probably crashes but not even that is sure. So the code should probably check shortly at runtime that all the strings are in the table upfront.
If the array is not changed at runtime, make it const so that the array itself ends up in flash.
const char * const msg[NUM_LANGUAGES][NUM_MESSAGES] =...
The leading const states that the strings the array points to are constant (which they are anyhow as those are string literals here).
While I in general not like C macros much, I've used macros in the past to initialize string tables and assign names to them.
E.g. (just pseudo code)
in a header stringtable.h
#define NUM_LANGUAGES 2
#define LANG_ENGLISH_INDEX 0
#define LANG_GERMAN_INDEX 1
#ifdef DEFINE_STRING_TABLE
extern const char* const [][NUM_LANGUAGES] = {
#define TABLE_ENTRY(name, eng, german) {(eng), (german) },
#else
enum stringTableIndex {
#define TABLE_ENTRY(name, eng, german) name,
#endif
TABLE_ENTRY(HELLO_STR, "Hello", "Hallo")
TABLE_ENTRY(WORLD_STR, "world", "Welt")
#ifdef DEFINE_STRING_TABLE
};
#else
};
#endif
And then in one dedicated file (say stringtable.c)
#define DEFINE_STRING_TABLE
#include "stringtable.h"
With such a setup the names and the indexes match automatically and I would hope other programmers can easily add their own strings. One downside is though that the table lists the translations for one string together, whereas the C99 style allows per language blocks.
Daniel