<commit_before>#include <utility>
#include "halley/text/i18n.h"
#include "halley/file_formats/config_file.h"

using namespace Halley;

I18N::I18N()
{
}

I18N::I18N(Resources& resources, I18NLanguage currentLanguage)
{
	loadStrings(resources);
	setCurrentLanguage(currentLanguage);
}

void I18N::update()
{
	for (auto& o: observers) {
		if (o.second.needsUpdate()) {
			o.second.update();
			loadLocalisation(o.second.getRoot());
		}
	}
}

void I18N::loadStrings(Resources& resources)
{
	for (auto& assetName : resources.enumerate<ConfigFile>()) {
		if (assetName.startsWith("strings/")) {
			resources.of<ConfigFile>().unload(assetName);
			loadLocalisationFile(*resources.get<ConfigFile>(assetName));
		}
	}
}

void I18N::setCurrentLanguage(const I18NLanguage& code)
{
	currentLanguage = code;
	++version;
}

void I18N::setFallbackLanguage(const I18NLanguage& code)
{
	fallbackLanguage = code;
}

void I18N::loadLocalisationFile(const ConfigFile& config)
{
	loadLocalisation(config.getRoot());
	observers[config.getAssetId()] = ConfigObserver(config);
}

void I18N::loadLocalisation(const ConfigNode& root)
{
	for (auto& language: root.asMap()) {
		auto langCode = I18NLanguage(language.first);
		auto& lang = strings[langCode];
		for (auto& e: language.second.asMap()) {
			lang[e.first] = e.second.asString();
		}
	}
	++version;
}

Vector<I18NLanguage> I18N::getLanguagesAvailable() const
{
	Vector<I18NLanguage> result;
	for (auto& e: strings) {
		result.push_back(e.first);
	}
	return result;
}

LocalisedString I18N::get(const String& key) const
{
	auto curLang = strings.find(currentLanguage);
	if (curLang != strings.end()) {
		auto i = curLang->second.find(key);
		if (i != curLang->second.end()) {
			return LocalisedString(*this, key, i->second);
		}
	}

	if (fallbackLanguage && fallbackLanguage.value() != currentLanguage) {
		auto defLang = strings.find(fallbackLanguage.value());
		if (defLang != strings.end()) {
			auto i = defLang->second.find(key);
			if (i != defLang->second.end()) {
				return LocalisedString(*this, key, i->second);
			}
		}
	}

	return LocalisedString(*this, key, "#MISSING#");
}

std::optional<LocalisedString> I18N::get(const String& key, const I18NLanguage& language) const
{
	auto curLang = strings.find(language);
	if (curLang != strings.end()) {
		auto i = curLang->second.find(key);
		if (i != curLang->second.end()) {
			return LocalisedString(*this, key, i->second);
		}
	}<commit_msg>Print key of missing i18n string in dev builds<commit_after>#include <utility>
#include "halley/text/i18n.h"
#include "halley/file_formats/config_file.h"

using namespace Halley;

I18N::I18N()
{
}

I18N::I18N(Resources& resources, I18NLanguage currentLanguage)
{
	loadStrings(resources);
	setCurrentLanguage(currentLanguage);
}

void I18N::update()
{
	for (auto& o: observers) {
		if (o.second.needsUpdate()) {
			o.second.update();
			loadLocalisation(o.second.getRoot());
		}
	}
}

void I18N::loadStrings(Resources& resources)
{
	for (auto& assetName : resources.enumerate<ConfigFile>()) {
		if (assetName.startsWith("strings/")) {
			resources.of<ConfigFile>().unload(assetName);
			loadLocalisationFile(*resources.get<ConfigFile>(assetName));
		}
	}
}

void I18N::setCurrentLanguage(const I18NLanguage& code)
{
	currentLanguage = code;
	++version;
}

void I18N::setFallbackLanguage(const I18NLanguage& code)
{
	fallbackLanguage = code;
}

void I18N::loadLocalisationFile(const ConfigFile& config)
{
	loadLocalisation(config.getRoot());
	observers[config.getAssetId()] = ConfigObserver(config);
}

void I18N::loadLocalisation(const ConfigNode& root)
{
	for (auto& language: root.asMap()) {
		auto langCode = I18NLanguage(language.first);
		auto& lang = strings[langCode];
		for (auto& e: language.second.asMap()) {
			lang[e.first] = e.second.asString();
		}
	}
	++version;
}

Vector<I18NLanguage> I18N::getLanguagesAvailable() const
{
	Vector<I18NLanguage> result;
	for (auto& e: strings) {
		result.push_back(e.first);
	}
	return result;
}

LocalisedString I18N::get(const String& key) const
{
	auto curLang = strings.find(currentLanguage);
	if (curLang != strings.end()) {
		auto i = curLang->second.find(key);
		if (i != curLang->second.end()) {
			return LocalisedString(*this, key, i->second);
		}
	}

	if (fallbackLanguage && fallbackLanguage.value() != currentLanguage) {
		auto defLang = strings.find(fallbackLanguage.value());
		if (defLang != strings.end()) {
			auto i = defLang->second.find(key);
			if (i != defLang->second.end()) {
				return LocalisedString(*this, key, i->second);
			}
		}
	}

#ifdef DEV_BUILD
	return LocalisedString(*this, key, "#MISSING:" + key + "#");
#else
	return LocalisedString(*this, key, "#MISSING#");
#endif
}

std::optional<LocalisedString> I18N::get(const String& key, const I18NLanguage& language) const
{
	auto curLang = strings.find(language);
	if (curLang != strings.end()) {
		auto i = curLang->second.find(key);
		if (i != curLang->second.end()) {
			return LocalisedString(*this, key, i->second);
		}
	}
{'fraction_non_alphanumeric': 0.16136136136136137, 'fraction_numerical': 0.017217217217217216, 'mean_word_length': 5.213930348258707, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '109072', 'n_tokens_mistral': 1904, 'n_tokens_neox': 1774, 'n_words': 335}