I added more details here because this part of the code is fully modable in the DLL and as such this post should be useful to anybody who wants to mod it.
This post applies to vanilla/unmodded behavior only! This can be rewritten to behave completely different if a mod has a modded DLL.
The language setup
The DLL has a global variable called NUM_LANGUAGES. It can be set with CvGameText::setNumLanguages(int iNum), though vanilla doesn't actually use this function.
The language menu is a python menu (hidden in the exe), which calls CvGameText::getNumLanguages(). This call can be modded both in the function and in the python interface (can point to a different function).
The names of the languages are named TXT_KEY_LANGUAGE_ + index, like TXT_KEY_LANGUAGE_0, TXT_KEY_LANGUAGE_1 etc.
GlobalDefinesALT has MAX_NUM_LANGUAGES. It sets the upper limit for how many languages the menu can handle (vanilla sets this to 100)
How NUM_LANGUAGES is set
It starts at 0.
When CvGameText::read() is called, iNumLanguages is set to NUM_LANGUAGES. If this is 0, it is set to MAX_NUM_LANGUAGES + 1 instead.
When reading a string, it loops iNumLanguages times. If it tries to read more languages than there are present in the xml, it will set NUM_LANGUAGES to match the number of languages present in xml.
Missing languages
When you expect 5 languages, but only see 1, odds are that there is one TXT_KEY somewhere, which only has English. NUM_LANGUAGES will then be set to 1. It doesn't matter how many languages you have in general, only how many you have in the TXT_KEY with the fewest languages.
Sadly the DLL doesn't tell or even store in memory which TXT_KEY is responsible for shortening the list. You have to mod the DLL to tell you or look through your text xml files.