diff --git a/Changelog.txt b/Changelog.txt index e3af63a..5aa4d9d 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,11 @@ + +- Ensure manual population changes always force eviction of households if required +- Don't force-update buildings on default changes += Clear visitplace cache on global commercial changes +- Refactor per-floor population preview calcs +- Update translation framework + + Version 2.0.4.2 - - Fix initial calculation mode vanilla choice sometimes not ensuring vanilla maximum populations diff --git a/Code/GUI/UILegacyCalcs.cs b/Code/GUI/UILegacyCalcs.cs index 886c399..7c65b0d 100644 --- a/Code/GUI/UILegacyCalcs.cs +++ b/Code/GUI/UILegacyCalcs.cs @@ -299,7 +299,7 @@ public void SelectionChanged(BuildingInfo building) } // Check to see if Ploppable RICO Revisited is controlling this building's population. - if (ModUtils.CheckRICOPopControl(building)) + if (AssemblyUtils.CheckRICOPopControl(building)) { messageLabel.text = Translations.Translate("RPR_CAL_RICO"); messageLabel.Show(); diff --git a/Code/GUI/UITitleBar.cs b/Code/GUI/UITitleBar.cs index 25bcfc8..bdfdd40 100644 --- a/Code/GUI/UITitleBar.cs +++ b/Code/GUI/UITitleBar.cs @@ -46,7 +46,7 @@ public void Setup() // Titlebar label. titleLabel = AddUIComponent(); titleLabel.relativePosition = new Vector2(50, 13); - titleLabel.text = RealPopMod.ModName; + titleLabel.text = Mod.ModName; // Close button. closeButton = AddUIComponent(); diff --git a/Code/GUI/UIVanillaCalcs.cs b/Code/GUI/UIVanillaCalcs.cs index afe7f4c..55dbda4 100644 --- a/Code/GUI/UIVanillaCalcs.cs +++ b/Code/GUI/UIVanillaCalcs.cs @@ -165,7 +165,7 @@ internal void SelectionChanged(BuildingInfo building) } // Check to see if Ploppable RICO Revisited is controlling this building's population. - if (ModUtils.CheckRICOPopControl(building)) + if (AssemblyUtils.CheckRICOPopControl(building)) { messageLabel.text = Translations.Translate("RPR_CAL_RICO"); messageLabel.Show(); diff --git a/Code/GUI/UIVolumetricPanel.cs b/Code/GUI/UIVolumetricPanel.cs index e6e714f..aed9b1a 100644 --- a/Code/GUI/UIVolumetricPanel.cs +++ b/Code/GUI/UIVolumetricPanel.cs @@ -362,7 +362,7 @@ internal void CalculateVolumetric(BuildingInfo building, LevelData levelData, Fl } // Show override labels if population is being overriden (population message text will clobber any previous floor override message, which is by design). - if (ModUtils.CheckRICOPopControl(building)) + if (AssemblyUtils.CheckRICOPopControl(building)) { // Overridden by Ploppable RICO Revisited. overridePopLabel.Show(); diff --git a/Code/Loading.cs b/Code/Loading.cs index ee23822..dd2679b 100644 --- a/Code/Loading.cs +++ b/Code/Loading.cs @@ -49,7 +49,7 @@ public override void OnCreated(ILoading loading) } // Check for mod conflicts. - if (ModUtils.IsModConflict()) + if (AssemblyUtils.IsModConflict()) { // Conflict detected. conflictingMod = true; @@ -64,13 +64,13 @@ public override void OnCreated(ILoading loading) if (!isModEnabled) { isModEnabled = true; - Logging.KeyMessage("version v", RealPopMod.Version, " loading"); + Logging.KeyMessage("version v", Mod.Version, " loading"); // Perform legacy datastore setup. XMLUtilsWG.Setup(); // Check for Ploppable RICO Revisited. - ModUtils.RICOReflection(); + AssemblyUtils.RICOReflection(); // Initialise volumetric datastores. EmploymentData.Setup(); @@ -118,7 +118,7 @@ public override void OnLevelLoaded(LoadMode mode) modConflictBox.AddParas(Translations.Translate("ERR_CON0"), Translations.Translate("RPR_ERR_CON0"), Translations.Translate("RPR_ERR_FAT"), Translations.Translate("ERR_CON1")); // Add conflicting mod name(s). - modConflictBox.AddList(ModUtils.conflictingModNames.ToArray()); + modConflictBox.AddList(AssemblyUtils.conflictingModNames.ToArray()); // Closing para. modConflictBox.AddParas(Translations.Translate("RPR_ERR_CON1")); diff --git a/Code/Logging.cs b/Code/Logging.cs index dc2d8e4..237ebab 100644 --- a/Code/Logging.cs +++ b/Code/Logging.cs @@ -54,7 +54,7 @@ internal static void LogException(Exception exception, params object[] messages) // Use StringBuilder for efficiency since we're doing a lot of manipulation here. // Start with mod name (to easily identify relevant messages), followed by colon to indicate start of actual message. message.Length = 0; - message.Append(RealPopMod.ModName); + message.Append(Mod.ModName); message.Append(": "); // Add each message parameter. @@ -94,7 +94,7 @@ private static void WriteMessage(string prefix, params object[] messages) // Use StringBuilder for efficiency since we're doing a lot of manipulation here. // Start with mod name (to easily identify relevant messages), followed by colon to indicate start of actual message. message.Length = 0; - message.Append(RealPopMod.ModName); + message.Append(Mod.ModName); message.Append(": "); // Append prefix. diff --git a/Code/Mod.cs b/Code/Mod.cs index fbf2cac..43cfcb7 100644 --- a/Code/Mod.cs +++ b/Code/Mod.cs @@ -5,7 +5,7 @@ namespace RealPop2 { - public class RealPopMod : IUserMod + public class Mod : IUserMod { // Public mod name and description. public string Name => ModName + " " + Version; @@ -13,7 +13,7 @@ public class RealPopMod : IUserMod // Internal and private name and version components. internal static string ModName => "Realistic Population 2"; - internal static string Version => "2.0.4.2"; + internal static string Version => AssemblyUtils.CurrentVersion; /// diff --git a/Code/Notifications/ListMessageBox.cs b/Code/Notifications/ListMessageBox.cs index 0fa6a14..5d413d7 100644 --- a/Code/Notifications/ListMessageBox.cs +++ b/Code/Notifications/ListMessageBox.cs @@ -22,7 +22,7 @@ public class ListMessageBox : MessageBoxBase public ListMessageBox() { // Set title. - Title = RealPopMod.ModName; + Title = Mod.ModName; // Add buttons. AddButtons(); diff --git a/Code/Notifications/WhatsNew.cs b/Code/Notifications/WhatsNew.cs index c920b39..d4b680c 100644 --- a/Code/Notifications/WhatsNew.cs +++ b/Code/Notifications/WhatsNew.cs @@ -79,7 +79,7 @@ internal static void ShowWhatsNew() { // Show messagebox. WhatsNewMessageBox messageBox = MessageBoxBase.ShowModal(); - messageBox.Title = RealPopMod.ModName + " " + RealPopMod.Version; + messageBox.Title = Mod.ModName + " " + Mod.Version; messageBox.DSAButton.eventClicked += (component, clickEvent) => DontShowAgain(); messageBox.SetMessages(whatsNewVersion, WhatsNewMessages); } diff --git a/Code/Notifications/WhatsNewMessageBox.cs b/Code/Notifications/WhatsNewMessageBox.cs index 23ab3f3..5b63e34 100644 --- a/Code/Notifications/WhatsNewMessageBox.cs +++ b/Code/Notifications/WhatsNewMessageBox.cs @@ -91,7 +91,7 @@ public VersionMessage() public void SetText(WhatsNewMessage message) { // Set version header and message text. - versionTitle = RealPopMod.ModName + " " + message.version.ToString(message.version.Build > 0 ? 3 : 2) + message.versionHeader; + versionTitle = Mod.ModName + " " + message.version.ToString(message.version.Build > 0 ? 3 : 2) + message.versionHeader; // Add message elements as separate list items. for (int i = 0; i < message.messages.Length; ++i) diff --git a/Code/Patches/Patcher.cs b/Code/Patches/Patcher.cs index 6f7de7e..1faf4ff 100644 --- a/Code/Patches/Patcher.cs +++ b/Code/Patches/Patcher.cs @@ -71,7 +71,7 @@ internal static void PatchABLC() if (HarmonyHelper.IsHarmonyInstalled) { // Try to get ABLC method. - MethodInfo ablcCustomUpgraded = ModUtils.ABLCCustomUpgraded(); + MethodInfo ablcCustomUpgraded = AssemblyUtils.ABLCCustomUpgraded(); if (ablcCustomUpgraded != null) { // Got method - apply patch. diff --git a/Code/Settings/CalculationTabs/DefaultsTabs/EmpDefaultsPanel.cs b/Code/Settings/CalculationTabs/DefaultsTabs/EmpDefaultsPanel.cs index 9b68d90..15af99e 100644 --- a/Code/Settings/CalculationTabs/DefaultsTabs/EmpDefaultsPanel.cs +++ b/Code/Settings/CalculationTabs/DefaultsTabs/EmpDefaultsPanel.cs @@ -46,9 +46,9 @@ protected override void Apply(UIComponent control, UIMouseEventParameter mouseEv PopData.instance.visitplaceCache.Clear(); // Clear RICO cache. - if (ModUtils.ricoClearAllWorkplaces != null) + if (AssemblyUtils.ricoClearAllWorkplaces != null) { - ModUtils.ricoClearAllWorkplaces.Invoke(null, null); + AssemblyUtils.ricoClearAllWorkplaces.Invoke(null, null); } } } diff --git a/Code/Settings/ModSettings.cs b/Code/Settings/ModSettings.cs index 23942f8..bf746a4 100644 --- a/Code/Settings/ModSettings.cs +++ b/Code/Settings/ModSettings.cs @@ -252,9 +252,9 @@ private static void ClearWorkplaceCaches() PopData.instance.visitplaceCache.Clear(); // Clear RICO cache too. - if (ModUtils.ricoClearAllWorkplaces != null) + if (AssemblyUtils.ricoClearAllWorkplaces != null) { - ModUtils.ricoClearAllWorkplaces.Invoke(null, null); + AssemblyUtils.ricoClearAllWorkplaces.Invoke(null, null); } } } diff --git a/Code/TranslationFramework/Language.cs b/Code/TranslationFramework/Language.cs index 7fd4194..5150073 100644 --- a/Code/TranslationFramework/Language.cs +++ b/Code/TranslationFramework/Language.cs @@ -8,18 +8,17 @@ namespace RealPop2 /// public class Language { - // Translation file keywords - language code and readable name. - public static readonly string CodeKey = "CODE"; - public static readonly string NameKey = "NAME"; + // Translation file keywords - readable name. + public static readonly string NameKey = "LANGUAGE"; // Dictionary of translations for this language. public Dictionary translationDictionary = new Dictionary(); - // Language unique name. - public string uniqueName = null; + // Language code. + public string code = null; - // Language human-readable name. + // Language human-readable display name. public string readableName = null; } } \ No newline at end of file diff --git a/Code/TranslationFramework/TranslationFramework.cs b/Code/TranslationFramework/TranslationFramework.cs deleted file mode 100644 index 4658bb3..0000000 --- a/Code/TranslationFramework/TranslationFramework.cs +++ /dev/null @@ -1,530 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Collections.Generic; -using ColossalFramework; -using ColossalFramework.Globalization; - - -namespace RealPop2 -{ - /// - /// Static class to provide translation interface. - /// - public static class Translations - { - // Instance reference. - private static Translator _translator; - - - /// - /// Static interface to instance's translate method. - /// - /// Key to translate - /// Translation (or key if translation failed) - public static string Translate(string key) => Instance.Translate(key); - - public static string CurrentLanguage - { - get - { - return Instance.CurrentLanguage; - } - set - { - Instance.SetLanguage(value); - } - } - - /// - /// Static interface to instance's language list property. - /// Returns an alphabetically-sorted (by unique name) string array of language display names, with an additional "system settings" item as the first item. - /// Useful for automatically populating drop-down language selection menus; works in conjunction with Index. - /// - public static string[] LanguageList => Instance.LanguageList; - - - /// - /// The current language index number (equals the index number of the language names list provied by LanguageList). - /// Useful for easy automatic drop-down language selection menus, working in conjunction with LanguageList: - /// Set to set the language to the equivalent LanguageList index. - /// Get to return the LanguageList index of the current languge. - /// - public static int Index - { - // Internal index is one less than here. - // I.e. internal index is -1 for system and 0 for first language, here we want 0 for system and 1 for first language. - // So we add one when getting and subtract one when setting. - get - { - return Instance.Index + 1; - } - set - { - Instance.SetLanguage(value - 1); - } - } - - - /// - /// On-demand initialisation of translator. - /// - /// Translator instance - private static Translator Instance - { - get - { - if (_translator == null) - { - _translator = new Translator(); - } - - return _translator; - } - } - } - - - /// - /// Handles translations. Framework by algernon, based off BloodyPenguin's framework. - /// - public class Translator - { - private Language systemLanguage = null; - private readonly SortedList languages; - private readonly string defaultLanguage = "en"; - private int currentIndex = -1; - - - /// - /// Returns the current zero-based index number of the current language setting. - /// Less than zero is 'use system setting'. - /// - public int Index => currentIndex; - - - /// - /// Returns the current language code if one has specifically been set; otherwise, return "default". - /// - public string CurrentLanguage => currentIndex < 0 ? "default" : languages.Values[currentIndex].uniqueName; - - - /// - /// Actions to update the UI on a language change go here. - /// - public void UpdateUILanguage() - { - Logging.Message("setting language to ", currentIndex < 0 ? "system" : languages.Values[currentIndex].uniqueName); - - // UI update code goes here. - - // TOOO: Add dynamic UI update. - } - - - /// - /// Returns an alphabetically-sorted (by code) array of language display names, with an additional "system settings" item as the first item. - /// - /// Readable language names in alphabetical order by unique name (language code) as string array - public string[] LanguageList - { - get - { - // Get list of readable language names. - List readableNames = languages.Values.Select((language) => language.readableName).ToList(); - - // Insert system settings item at the start. - readableNames.Insert(0, Translate("TRN_SYS")); - - // Return out list as a string array. - return readableNames.ToArray(); - } - } - - - /// - /// Constructor. - /// - public Translator() - { - // Initialise languages list. - languages = new SortedList(); - - // Load translation files. - LoadLanguages(); - - // Event handler to update the current language when system locale changes. - LocaleManager.eventLocaleChanged += SetSystemLanguage; - } - - - /// - /// Returns the translation for the given key in the current language. - /// - /// Translation key to transate - /// Translation - public string Translate(string key) - { - Language currentLanguage; - - - // Check to see if we're using system settings. - if (currentIndex < 0) - { - // Using system settings - initialise system language if we haven't already. - if (systemLanguage == null) - { - SetSystemLanguage(); - } - - currentLanguage = systemLanguage; - } - else - { - currentLanguage = languages.Values[currentIndex]; - } - - // Check that a valid current language is set. - if (currentLanguage != null) - { - // Check that the current key is included in the translation. - if (currentLanguage.translationDictionary.ContainsKey(key)) - { - // All good! Return translation. - return currentLanguage.translationDictionary[key]; - } - else - { - Logging.Message("no translation for language ", currentLanguage.uniqueName, " found for key " + key); - - // Attempt fallack translation. - return FallbackTranslation(currentLanguage.uniqueName, key); - } - } - else - { - Logging.Error("no current language when translating key ", key); - } - - // If we've made it this far, something went wrong; just return the key. - return key; - } - - - /// - /// Sets the current system language; sets to null if none. - /// - public void SetSystemLanguage() - { - // Make sure Locale Manager is ready before calling it. - if (LocaleManager.exists) - { - // Try to set our system language from system settings. - try - { - // Get new locale id. - string newLanguageCode = LocaleManager.instance.language; - - // Check to see if we have a translation for this language code; if not, we revert to default. - if (!languages.ContainsKey(newLanguageCode)) - { - newLanguageCode = defaultLanguage; - } - - // If we've already been set to this locale, do nothing. - if (systemLanguage != null && systemLanguage.uniqueName == newLanguageCode) - { - return; - } - - // Set the new system language, - systemLanguage = languages[newLanguageCode]; - - // If we're using system language, update the UI. - if (currentIndex < 0) - { - UpdateUILanguage(); - } - - // All done. - return; - } - catch (Exception e) - { - // Don't really care. - Logging.LogException(e, "exception setting system language"); - } - } - - // If we made it here, there's no valid system language. - systemLanguage = null; - } - - - /// - /// Sets the current language to the provided language code. - /// If the key isn't in the list of loaded translations, then the system default is assigned instead(IndexOfKey returns -1 if key not found). - /// - /// Language unique name (code) - public void SetLanguage(string uniqueName) => SetLanguage(languages.IndexOfKey(uniqueName)); - - - /// - /// Sets the current language to the supplied index number. - /// If index number is invalid (out-of-bounds) then current language is set to -1 (system language setting). - /// - /// 1-based language index number (negative values will use system language settings instead) - public void SetLanguage(int index) - { - // Don't do anything if no languages have been loaded. - if (languages != null && languages.Count > 0) - { - // Bounds check; if out of bounds, use -1 (system language) instead. - int newIndex = index >= languages.Count ? -1 : index; - - // Change the language if what we've done is new. - if (newIndex != currentIndex) - { - currentIndex = newIndex; - - // Trigger UI update. - UpdateUILanguage(); - } - } - } - - - /// - /// Attempts to find a fallback language translation in case the primary one fails (for whatever reason). - /// First tries a shortened version of the current reference (e.g. zh-tw -> zh), then system language, then default language. - /// If all that fails, it just returns the raw key. - /// - /// Language code that was previously attempted - /// Fallback translation if successful, or raw key if failed - private string FallbackTranslation(string attemptedLanguage, string key) - { - // First check to see if there is a shortened version of this language id (e.g. zh-tw -> zh). - if (attemptedLanguage.Length > 2) - { - string newName = attemptedLanguage.Substring(0, 2); - - if (languages.ContainsKey(newName)) - { - Language fallbackLanguage = languages[newName]; - if (fallbackLanguage.translationDictionary.ContainsKey(key)) - { - // All good! Return translation. - return fallbackLanguage.translationDictionary[key]; - } - } - } - - // Secondly, try to use system language if we're not already doing so. - if (currentIndex > 0 && systemLanguage != null && attemptedLanguage != systemLanguage.uniqueName) - { - if (systemLanguage.translationDictionary.ContainsKey(key)) - { - // All good! Return translation. - return systemLanguage.translationDictionary[key]; - } - } - - // Final attempt - try default language. - try - { - Language fallbackLanguage = languages[defaultLanguage]; - return fallbackLanguage.translationDictionary[key]; - } - catch (Exception e) - { - // Don't care. Just log the exception, as we really should have a default language. - Logging.LogException(e, "exception attempting fallback translation"); - } - - // At this point we've failed; just return the key. - return key; - } - - - /// - /// Loads languages from CSV files. - /// - private void LoadLanguages() - { - // Clear existing dictionary. - languages.Clear(); - - // Get the current assembly path and append our locale directory name. - string assemblyPath = ModUtils.GetAssemblyPath(); - if (!assemblyPath.IsNullOrWhiteSpace()) - { - string localePath = Path.Combine(assemblyPath, "Translations"); - Logging.Message("loading translations from ", localePath); - - // Ensure that the directory exists before proceeding. - if (Directory.Exists(localePath)) - { - // Load each file in directory and attempt to deserialise as a translation file. - string[] translationFiles = Directory.GetFiles(localePath); - foreach (string translationFile in translationFiles) - { - // Skip anything that's not marked as a .csv file. - if (!translationFile.EndsWith(".csv")) - { - continue; - } - - Logging.Message("reading translation file ", translationFile); - - // Read file. - FileStream fileStream = new FileStream(translationFile, FileMode.Open, FileAccess.Read); - using (StreamReader reader = new StreamReader(fileStream)) - { - // Create new language instance for this file. - Language thisLanguage = new Language(); - string key = null; - bool quoting = false; - - // Iterate through each line of file. - string line = reader.ReadLine(); - while (line != null) - { - // Are we parsing quoted lines? - if (quoting) - { - // Parsing a quoted line - make sure we have a valid current key. - if (!key.IsNullOrWhiteSpace()) - { - // Yes - if the line ends with a quote, trim the quote and add to existing dictionary entry and stop quoting. - if (line.EndsWith("\"")) - { - quoting = false; - thisLanguage.translationDictionary[key] += line.Substring(0, line.Length - 1); - } - else - { - // Line doesn't end with a quote - add line to existing dictionary entry and keep going. - thisLanguage.translationDictionary[key] += line + Environment.NewLine; - } - } - } - else - { - // Not parsing quoted line - look for comma separator on this line. - int commaPos = line.IndexOf(","); - if (commaPos > 0) - { - // Comma found - split line into key and value, delimited by first comma. - key = line.Substring(0, commaPos); - string value = line.Substring(commaPos + 1); - - // Don't do anything if either key or value is invalid. - if (!key.IsNullOrWhiteSpace() && !value.IsNullOrWhiteSpace()) - { - // Trim quotes off keys. - if (key.StartsWith("\"")) - { - // Starts with quotation mark - if it also ends in a quotation mark, strip both quotation marks. - if (key.EndsWith("\"")) - { - key = key.Substring(1, key.Length - 2); - } - else - { - // Doesn't end in a quotation mark, so just strip leading quotation mark. - key = key.Substring(1); - } - } - - // Does this value start with a quotation mark? - if (value.StartsWith("\"")) - { - // Starts with quotation mark - if it also ends in a quotation mark, strip both quotation marks. - if (value.EndsWith("\"")) - { - value = value.Substring(1, value.Length - 2); - } - else - { - // Doesn't end in a quotation mark, so we've (presumably) got a multi-line quoted entry - // Flag quoting mode and set initial value to start of quoted string (less leading quotation mark), plus trailing newline. - quoting = true; - value = value.Substring(1) + Environment.NewLine; - } - } - - // Check for reserved keywords. - if (key.Equals(Language.CodeKey)) - { - // Language code. - thisLanguage.uniqueName = value; - } - else if (key.Equals(Language.NameKey)) - { - // Language readable name. - thisLanguage.readableName = value; - } - else - { - // Try to add key/value pair to translation dictionary, if it's valid. - if (!value.IsNullOrWhiteSpace()) - { - // Check for duplicates. - if (!thisLanguage.translationDictionary.ContainsKey(key)) - { - thisLanguage.translationDictionary.Add(key, value); - } - else - { - Logging.Error("duplicate translation key ", key, " in file ", translationFile); - } - } - } - } - } - else - { - // No comma delimiter found - append to previous line (if last-used key is valid). - if (!key.IsNullOrWhiteSpace()) - { - thisLanguage.translationDictionary[key] += line; - } - } - } - - // Read next line. - line = reader.ReadLine(); - } - - // Did we get a valid dictionary from this? - if (thisLanguage.uniqueName != null && thisLanguage.readableName != null && thisLanguage.translationDictionary.Count > 0) - { - // Yes - add to languages dictionary. - if (!languages.ContainsKey(thisLanguage.uniqueName)) - { - languages.Add(thisLanguage.uniqueName, thisLanguage); - } - else - { - Logging.Error("duplicate translation file for language ", thisLanguage.uniqueName); - } - } - else - { - Logging.Error("file ", translationFile, " did not produce a valid translation dictionary"); - } - } - } - } - else - { - Logging.Error("translations directory not found"); - } - } - else - { - Logging.Error("assembly path was empty"); - } - } - } -} \ No newline at end of file diff --git a/Code/TranslationFramework/Translations.cs b/Code/TranslationFramework/Translations.cs new file mode 100644 index 0000000..af3e02c --- /dev/null +++ b/Code/TranslationFramework/Translations.cs @@ -0,0 +1,83 @@ +namespace RealPop2 +{ + /// + /// Static class to provide translation interface. + /// + public static class Translations + { + // Instance reference. + private static Translator _translator; + + + /// + /// The current language code. + /// + public static string CurrentLanguage + { + get + { + return Instance.CurrentLanguage; + } + set + { + Instance.SetLanguage(value); + } + } + + + /// + /// Static interface to instance's language list property. + /// Returns an alphabetically-sorted (by unique name) string array of language display names, with an additional "system settings" item as the first item. + /// Useful for automatically populating drop-down language selection menus; works in conjunction with Index. + /// + public static string[] LanguageList => Instance.LanguageList; + + + /// + /// The current language index number (equals the index number of the language names list provied by LanguageList). + /// Useful for easy automatic drop-down language selection menus, working in conjunction with LanguageList: + /// Set to set the language to the equivalent LanguageList index. + /// Get to return the LanguageList index of the current languge. + /// + public static int Index + { + // Internal index is one less than here. + // I.e. internal index is -1 for system and 0 for first language, here we want 0 for system and 1 for first language. + // So we add one when getting and subtract one when setting. + get + { + return Instance.Index + 1; + } + set + { + Instance.SetLanguage(value - 1); + } + } + + + /// + /// On-demand initialisation of translator. + /// + /// Translator instance + private static Translator Instance + { + get + { + if (_translator == null) + { + _translator = new Translator(); + } + + return _translator; + } + } + + + /// + /// Static interface to instance's translate method. + /// + /// Key to translate + /// Translation (or key if translation failed) + public static string Translate(string key) => Instance.Translate(key); + } +} \ No newline at end of file diff --git a/Code/TranslationFramework/Translator.cs b/Code/TranslationFramework/Translator.cs new file mode 100644 index 0000000..6265351 --- /dev/null +++ b/Code/TranslationFramework/Translator.cs @@ -0,0 +1,529 @@ +using ColossalFramework; +using ColossalFramework.Globalization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + + +namespace RealPop2 +{ + /// + /// Handles translations. Framework by algernon, based off BloodyPenguin's framework. + /// + public class Translator + { + private Language systemLanguage = null; + private readonly SortedList languages; + private readonly string defaultLanguage = "en-EN"; + private int currentIndex = -1; + + // Last recorded system language. + private string systemLangaugeCode; + + + /// + /// Returns the current zero-based index number of the current language setting. + /// Less than zero is 'use system setting'. + /// + public int Index => currentIndex; + + + /// + /// Returns the current language code if one has specifically been set; otherwise, return "default". + /// + public string CurrentLanguage => currentIndex < 0 ? "default" : languages.Values[currentIndex].code; + + + /// + /// Actions to update the UI on a language change go here. + /// + public void UpdateUILanguage() + { + // UI update code goes here. + + // TOOO: Add dynamic UI update. + } + + + /// + /// Returns an alphabetically-sorted (by code) array of language display names, with an additional "system settings" item as the first item. + /// + /// Readable language names in alphabetical order by unique name (language code) as string array + public string[] LanguageList + { + get + { + // Get list of readable language names. + List readableNames = languages.Values.Select((language) => language.readableName).ToList(); + + // Insert system settings item at the start. + readableNames.Insert(0, Translate("TRN_SYS")); + + // Return out list as a string array. + return readableNames.ToArray(); + } + } + + + /// + /// Constructor. + /// + public Translator() + { + // Initialise languages list. + languages = new SortedList(); + + // Load translation files. + LoadLanguages(); + + // Set initial system language reference. + systemLangaugeCode = string.Empty; + } + + + /// + /// Returns the translation for the given key in the current language. + /// + /// Translation key to transate + /// Translation + public string Translate(string key) + { + Language currentLanguage; + + // Check to see if we're using system settings. + if (currentIndex < 0) + { + // Using system settings - initialise system language if we haven't already, or if the system language has changed since last time. + if (LocaleManager.exists & (LocaleManager.instance.language != systemLangaugeCode | systemLanguage == null)) + { + SetSystemLanguage(); + } + currentLanguage = systemLanguage; + } + else + { + currentLanguage = languages.Values[currentIndex]; + } + + // Check that a valid current language is set. + if (currentLanguage != null) + { + // Check that the current key is included in the translation. + if (currentLanguage.translationDictionary.ContainsKey(key)) + { + // All good! Return translation. + return currentLanguage.translationDictionary[key]; + } + else + { + // Lookup failed - fallack translation. + Logging.Message("no translation for language ", currentLanguage.code, " found for key " + key); + return FallbackTranslation(currentLanguage.code, key); + } + } + else + { + Logging.Error("no current language when translating key ", key); + } + + // If we've made it this far, something went wrong; just return the key. + return key; + } + + + /// + /// Sets the current system language; sets to null if none. + /// + public void SetSystemLanguage() + { + // Make sure Locale Manager is ready before calling it. + if (LocaleManager.exists) + { + // Try to set our system language from system settings. + try + { + // Get new locale id. + string newLanguageCode = LocaleManager.instance.language; + + // If we've already been set to this locale, do nothing. + if (systemLanguage != null & systemLangaugeCode == newLanguageCode) + { + return; + } + + // Set the new system language, + Logging.Message("game language is ", newLanguageCode); + systemLangaugeCode = newLanguageCode; + systemLanguage = FindLanguage(newLanguageCode); + + // If we're using system language, update the UI. + if (currentIndex < 0) + { + UpdateUILanguage(); + } + + // All done. + return; + } + catch (Exception e) + { + // Don't really care. + Logging.LogException(e, "exception setting system language"); + } + } + + // If we made it here, there's no valid system language. + systemLanguage = null; + } + + + /// + /// Sets the current language to the provided language code. + /// If the key isn't in the list of loaded translations, then the system default is assigned instead. + /// + /// Language code + public void SetLanguage(string languageCode) + { + Logging.Message("setting language to ", languageCode); + + // Default (game) language. + if (languageCode == "default") + { + SetLanguage(-1); + return; + } + + // Try for direct match. + if (languages.ContainsKey(languageCode)) + { + SetLanguage(languages.IndexOfKey(languageCode)); + return; + } + + // No direct match found - attempt to find any other suitable translation file (code matches first two letters). + string shortCode = languageCode.Substring(0, 2); + foreach (KeyValuePair entry in languages) + { + if (entry.Key.StartsWith(shortCode)) + { + // Found an alternative. + Logging.Message("using language ", entry.Key, " as replacement for unknown language code ", languageCode); + SetLanguage(languages.IndexOfKey(entry.Key)); + return; + } + } + + // If we got here, no match was found; revert to system language. + Logging.Message("no suitable translation file for language ", languageCode, " was found; reverting to game default"); + SetLanguage(-1); + } + + + /// + /// Sets the current language to the supplied index number. + /// If index number is invalid (out-of-bounds) then current language is set to -1 (system language setting). + /// + /// 1-based language index number (negative values will use system language settings instead) + public void SetLanguage(int index) + { + // Don't do anything if no languages have been loaded. + if (languages != null && languages.Count > 0) + { + // Bounds check; if out of bounds, use -1 (system language) instead. + int newIndex = index >= languages.Count ? -1 : index; + + // Change the language if what we've done is new. + if (newIndex != currentIndex) + { + currentIndex = newIndex; + + // Trigger UI update. + UpdateUILanguage(); + } + } + } + + + /// + /// Attempts to find the most appropriate translation file for the specified language code. + /// An exact match is attempted first; then a match with the first available language with the same two intial characters. + /// e.g. 'zh' will match to 'zh', 'zh-CN' or 'zh-TW' (in that order), or 'zh-CN' will match to 'zh-CN', 'zh' or 'zh-TW' (in that order). + /// If no match is made,the default language will be returned. + /// + /// Language code to match + /// Matched language code correspondign to a loaded translation file + private Language FindLanguage(string languageCode) + { + // First attempt to find the language code as-is. + if (languages.TryGetValue(languageCode, out Language language)) + { + return language; + } + + // If that fails, take the first two characters of the provided code and match with the first language code we have starting with those two letters. + // This will automatically prioritise any translations with only two letters (e.g. 'en' takes priority over 'en-US'), + KeyValuePair firstMatch = languages.FirstOrDefault(x => x.Key.StartsWith(languageCode.Substring(0, 2))); + if (!string.IsNullOrEmpty(firstMatch.Key)) + { + // Found one - return translation. + Logging.KeyMessage("using translation file ", firstMatch.Key, " for language ", languageCode); + return firstMatch.Value; + } + + // Fall back to default language. + Logging.Error("no translation file found for language ", languageCode, "; reverting to ", defaultLanguage); + return languages[defaultLanguage]; + } + + + /// + /// Attempts to find a fallback language translation in case the primary one fails (for whatever reason). + /// + /// Language code that was previously attempted + /// Fallback translation if successful, or raw key if failed + private string FallbackTranslation(string attemptedLanguage, string key) + { + try + { + // Attempt to find any other suitable translation file (code matches first two letters). + string shortCode = attemptedLanguage.Substring(0, 2); + foreach (KeyValuePair entry in languages) + { + if (entry.Key.StartsWith(shortCode) && entry.Value.translationDictionary.TryGetValue(key, out string result)) + { + // Found an alternative. + return result; + } + } + + // No alternative was found - return default language. + return languages[defaultLanguage].translationDictionary[key]; + + } + catch (Exception e) + { + // Don't care. Just log the exception, as we really should have a default language. + Logging.LogException(e, "exception attempting fallback translation"); + } + + // At this point we've failed; just return the key. + return key; + } + + + /// + /// Loads languages from CSV files. + /// + private void LoadLanguages() + { + // Clear existing dictionary. + languages.Clear(); + + // Get the current assembly path and append our locale directory name. + string assemblyPath = AssemblyUtils.AssemblyPath; + if (assemblyPath.IsNullOrWhiteSpace()) + { + Logging.Error("assembly path was empty"); + return; + } + + string localePath = Path.Combine(assemblyPath, "Translations"); + + // Ensure that the directory exists before proceeding. + if (!Directory.Exists(localePath)) + { + Logging.Error("translations directory not found"); + return; + } + + // Load each file in directory and attempt to deserialise as a translation file. + string[] translationFiles = Directory.GetFiles(localePath); + foreach (string translationFile in translationFiles) + { + // Skip anything that's not marked as a .csv file. + if (!translationFile.EndsWith(".csv")) + { + continue; + } + + // Read file. + try + { + FileStream fileStream = new FileStream(translationFile, FileMode.Open, FileAccess.Read); + using (StreamReader reader = new StreamReader(fileStream)) + { + // Create new language instance for this file. + Language thisLanguage = new Language + { + // Language code is filename. + code = Path.GetFileNameWithoutExtension(translationFile), + }; + + // Parsing fields. + StringBuilder builder = new StringBuilder(); + string key = null; + bool quoting = false, parseKey = true; + + // Iterate through each line of file. + string line = reader.ReadLine(); + while (line != null) + { + // Iterate through each character in line. + for (int i = 0; i < line.Length; ++i) + { + // Local reference. + char thisChar = line[i]; + + // Are we parsing quoted text? + if (quoting) + { + // Is this character a quote? + if (thisChar == '"') + { + // Is this a double quote? + int j = i + 1; + if (j < line.Length && line[j] == '"') + { + // Yes - append single quote to output and continue. + i = j; + builder.Append('"'); + continue; + } + + // It's a single quote - stop quoting here. + quoting = false; + + // If we're parsing a value, this is also the end of parsing this line (discard everything else). + if (!parseKey) + { + break; + } + } + else + { + // Not a closing quote - just append character to our parsed value. + builder.Append(thisChar); + } + } + else + { + // Not parsing quoted text - is this a comma? + if (thisChar == ',') + { + // Comma - if we're parsing a value, this is also the end of parsing this line (discard everything else). + if (!parseKey) + { + break; + } + + // Otherwise, what we've parsed is the key - store value and reset the builder. + parseKey = false; + key = builder.ToString(); + builder.Length = 0; + } + else if (thisChar == '"' & builder.Length == 0) + { + // If this is a quotation mark at the start of a field (immediately after comma), then we start parsing this as quoted text. + quoting = true; + } + else + { + // Otherwise, just append character to our parsed string. + builder.Append(thisChar); + } + } + } + + // Finished looping through chars - are we still parsing quoted text? + if (quoting) + { + // Yes; continue, after adding a newline. + builder.AppendLine(); + goto NextLine; + } + + // Was key empty? + if (key.IsNullOrWhiteSpace()) + { + Logging.Error("invalid key in line ", line); + goto Reset; + } + + // Did we get two delimited fields (key and value?) + if (parseKey | builder.Length == 0) + { + Logging.Error("no value field found in line ", line); + goto Reset; + } + + // Convert value to string and reset builder. + string value = builder.ToString(); + builder.Length = 0; + + // Check if this entry is the language entry. + if (key.Equals(Language.NameKey)) + { + // Language readable name. + thisLanguage.readableName = value; + } + else + { + // Normal entry - check for duplicates. + if (!thisLanguage.translationDictionary.ContainsKey(key)) + { + thisLanguage.translationDictionary.Add(key, value); + } + else + { + Logging.Error("duplicate translation key ", key, " in file ", translationFile); + } + } + + Reset: + // Reset for next line. + parseKey = true; + + NextLine: + // Read next line. + line = reader.ReadLine(); + } + + // Did we get a valid dictionary from this? + if (thisLanguage.code != null && thisLanguage.translationDictionary.Count > 0) + { + // Yes - add to languages dictionary. + + // If we didn't get a readable name, use the key instead. + if (thisLanguage.readableName.IsNullOrWhiteSpace()) + { + thisLanguage.readableName = thisLanguage.code; + } + + // Check for duplicates. + if (!languages.ContainsKey(thisLanguage.code)) + { + Logging.Message("read translation file ", translationFile, " with language ", thisLanguage.code, " (", thisLanguage.readableName, ") with ", thisLanguage.translationDictionary.Count, " entries"); + languages.Add(thisLanguage.code, thisLanguage); + } + else + { + Logging.Error("duplicate translation file for language ", thisLanguage.code); + } + } + else + { + Logging.Error("file ", translationFile, " did not produce a valid translation dictionary"); + } + } + } + catch (Exception e) + { + // Don't let a single exception stop us; keep going through remaining files. + Logging.LogException(e, "exception reading translation file ", translationFile); + } + } + } + } +} \ No newline at end of file diff --git a/Code/Utils/ModUtils.cs b/Code/Utils/AssemblyUtils.cs similarity index 81% rename from Code/Utils/ModUtils.cs rename to Code/Utils/AssemblyUtils.cs index 8b4264a..a810e92 100644 --- a/Code/Utils/ModUtils.cs +++ b/Code/Utils/AssemblyUtils.cs @@ -1,18 +1,17 @@ -using System; -using System.IO; +using ColossalFramework.Plugins; +using ICities; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Collections.Generic; -using ICities; -using ColossalFramework.Plugins; namespace RealPop2 { /// - /// Class that manages interactions with other mods, including compatibility and functionality checks. + /// Class that manages interactions with assemblies, including compatibility and functionality checks. /// - internal static class ModUtils + internal static class AssemblyUtils { // RICO methods. internal static MethodInfo ricoPopManaged; @@ -22,40 +21,77 @@ internal static class ModUtils // List of conflcting mod names. internal static List conflictingModNames; + // Mod assembly path cache. + private static string assemblyPath = null; + + + /// + /// Returns the current mod version as a string, leaving off any trailing zero versions for build and revision. + /// + internal static string CurrentVersion + { + get + { + Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version; + + if (currentVersion.Revision != 0) + { + return currentVersion.ToString(4); + } + else if (currentVersion.Build != 0) + { + return currentVersion.ToString(3); + } + else + { + return currentVersion.ToString(2); + } + } + } + /// /// Returns the filepath of the current mod assembly. /// /// Mod assembly filepath - internal static string GetAssemblyPath() + internal static string AssemblyPath { - // Get list of currently active plugins. - IEnumerable plugins = PluginManager.instance.GetPluginsInfo(); - - // Iterate through list. - foreach (PluginManager.PluginInfo plugin in plugins) + get { - try + // Return cached path if it exists. + if (assemblyPath != null) + { + return assemblyPath; + } + + // No path cached - get list of currently active plugins. + IEnumerable plugins = PluginManager.instance.GetPluginsInfo(); + + // Iterate through list. + foreach (PluginManager.PluginInfo plugin in plugins) { - // Get all (if any) mod instances from this plugin. - IUserMod[] mods = plugin.GetInstances(); + try + { + // Get all (if any) mod instances from this plugin. + IUserMod[] mods = plugin.GetInstances(); - // Check to see if the primary instance is this mod. - if (mods.FirstOrDefault() is RealPopMod) + // Check to see if the primary instance is this mod. + if (mods.FirstOrDefault() is Mod) + { + // Found it! Return path. + return plugin.modPath; + } + } + catch { - // Found it! Return path. - return plugin.modPath; + // Don't care. } } - catch - { - // Don't care. - } - } - // If we got here, then we didn't find the assembly. - Logging.Message("assembly path not found"); - throw new FileNotFoundException(RealPopMod.ModName + ": assembly path not found!"); + // If we got here, then we didn't find the assembly. + Logging.Error("assembly path not found"); + return null; + } } diff --git a/Code/Utils/TextureUtils.cs b/Code/Utils/TextureUtils.cs index e5210a6..e5c9055 100644 --- a/Code/Utils/TextureUtils.cs +++ b/Code/Utils/TextureUtils.cs @@ -121,7 +121,7 @@ private static Texture2D LoadTexture(string fileName) /// Read-only file stream private static Stream OpenResourceFile(string fileName) { - string path = Path.Combine(ModUtils.GetAssemblyPath(), "Resources"); + string path = Path.Combine(AssemblyUtils.AssemblyPath, "Resources"); return File.OpenRead(Path.Combine(path, fileName)); } } diff --git a/Code/VolumetricData/CalcData.cs b/Code/VolumetricData/CalcData.cs index 19fca97..1c32d3a 100644 --- a/Code/VolumetricData/CalcData.cs +++ b/Code/VolumetricData/CalcData.cs @@ -404,9 +404,9 @@ protected void RefreshPrefab(BuildingInfo prefab) PopData.instance.visitplaceCache.Remove(prefab); // Force RICO refresh, if we're using Ploppable RICO Revisited. - if (ModUtils.ricoClearWorkplace != null) + if (AssemblyUtils.ricoClearWorkplace != null) { - ModUtils.ricoClearWorkplace.Invoke(null, new object[] { prefab }); + AssemblyUtils.ricoClearWorkplace.Invoke(null, new object[] { prefab }); } } diff --git a/Realistic Population Revisited.csproj b/Realistic Population Revisited.csproj index 3c3cc27..525dc3c 100644 --- a/Realistic Population Revisited.csproj +++ b/Realistic Population Revisited.csproj @@ -9,6 +9,7 @@ $(AssemblySearchPaths); $(ManagedDLLPath); + RealPopRevisited diff --git a/Translations/da-DK.csv b/Translations/da-DK.csv index bb8f3b3..186d069 100644 --- a/Translations/da-DK.csv +++ b/Translations/da-DK.csv @@ -1,5 +1,4 @@ -"CODE","da" -"NAME","Dansk" +"LANGUAGE","Dansk" "RPR_NAME","Realistisk Befolkning 2" "RPR_DESC","Mere realistiske indbyggertal i bygninger. (baseret på størrelse og brugsbehov)" "RPR_REALPOP","Realistisk Befolkning" diff --git a/Translations/de-DE.csv b/Translations/de-DE.csv index 306cc1d..4e8af26 100644 --- a/Translations/de-DE.csv +++ b/Translations/de-DE.csv @@ -1,5 +1,4 @@ -"CODE","de" -"NAME","Deutsch" +"LANGUAGE","Deutsch" "RPR_NAME","Realistic Population 2" "RPR_DESC","Mehr realistische Gebäudebevölkerung (basierend auf der Gebäudegröße) und Versorgungsbedürfnisse." "RPR_REALPOP","Realistische Bevölkerung" diff --git a/Translations/en.csv b/Translations/en.csv index 6b1f16c..5bc9cb2 100644 --- a/Translations/en.csv +++ b/Translations/en.csv @@ -1,315 +1,314 @@ -CODE,en -NAME,English -RPR_NAME,Realistic Population 2 -RPR_DESC,More realistic building populations (based on building size) and utility needs. -RPR_REALPOP,Realistic Population -RPR_INF_VIS,visitors -RPR_PCK_DEF, (default) -RPR_PCK_NAM,Calculations -RPR_PCK_VAN_NAM,Vanilla (base game) -RPR_PCK_VAN_DES,Calculations as used by the base (unmodded) game. -RPR_PCK_LEG_NAM,Legacy WG calculations -RPR_PCK_LEG_DES,Old calculations used by versions 1.x of this mod and Whitefang Greytail's original Realistic Population and Consumption mod. -RPR_PCK_RLS_NAM,Single dwellings -RPR_PCK_RLS_DES,One household per building. -RPR_PCK_RLD_NAM,Duplexes -RPR_PCK_RLD_DES,Two households per building. -RPR_PCK_RUH_NAM,US highrise -RPR_PCK_RUH_DES,Average US apartments and condos. -RPR_PCK_REM_NAM,European apartments (modern) -RPR_PCK_REM_DES,European mid-rise and wall-to-wall apartments (medium density; modern buildings). -RPR_PCK_REO_NAM,European apartments (older) -RPR_PCK_REO_DES,European mid-rise and wall-to-wall apartments (high density; older buildings). -RPR_PCK_CUS_NAM,US retail -RPR_PCK_CUS_DES,General commercial retail density typical of USA. -RPR_PCK_CUK_NAM,UK retail -RPR_PCK_CUK_DES,General commercial retail density typical of UK. -RPR_PCK_CRW_NAM,Superstores -RPR_PCK_CRW_DES,Non-food superstores and retail warehouses. -RPR_PCK_THT_NAM,Hotels -RPR_PCK_THT_DES,Hotels and motels. -RPR_PCK_LFD_NAM,Food service -RPR_PCK_LFD_DES,Restaurants and cafes. -RPR_PCK_LEN_NAM,Entertainment -RPR_PCK_LEN_DES,"Amusement, fitness, and entertainment centers." -RPR_PCK_LCN_NAM,Cinemas -RPR_PCK_LCN_DES,Cinemas and theaters. -RPR_PCK_ILG_NAM,Light industry -RPR_PCK_ILG_DES,Light industries and business parks. -RPR_PCK_IMN_NAM,Manufacturing -RPR_PCK_IMN_DES,Industrial manufacturing. -RPR_PCK_IWL_NAM,Small warehouses -RPR_PCK_IWL_DES,Warehouses for local distribution - lots of storage and low employment density. -RPR_PCK_IWN_NAM,Large warehouses -RPR_PCK_IWN_DES,Warehouses for national distribution - lots of storage and very low employment density. -RPR_PCK_OCP_NAM,Corporate offices -RPR_PCK_OCP_DES,"Corporate, professional services, and public sector offices." -RPR_PCK_OFN_NAM,Finance offices -RPR_PCK_OFN_DES,Financial offices; higher density than typical corporate offices -RPR_PCK_OCS_NAM,Call centers -RPR_PCK_OCS_DES,Call centers and other tightly-packed offices -RPR_PCK_ODT_NAM,Datacenters -RPR_PCK_ODT_DES,Datacenters with co-location facilities -RPR_PCK_SSB_NAM,Surburban school -RPR_PCK_SSB_DES,Suburban schools -RPR_PCK_SML_NAM,MN low -RPR_PCK_SML_DES,Minnesota Dept of Education guide - lowest density -RPR_PCK_SMM_NAM,MN medium -RPR_PCK_SMM_DES,Minnesota Dept of Education guide - medium density -RPR_PCK_SMH_NAM,MN high -RPR_PCK_SMH_DES,Minnesota Dept of Education guide - highest density -RPR_PCK_SUK_NAM,UK standard -RPR_PCK_SUK_DES,Standard planning densities for the UK. -RPR_PCK_FDF_NAM,Generic -RPR_PCK_FDF_DES,Standard 3m floor height. -RPR_PCK_FHO_NAM,Standalone houses -RPR_PCK_FHO_DES,Detached residential dwellings. -RPR_PCK_FDL_NAM,Lobbies -RPR_PCK_FDL_DES,Buildings with lobbies and/or elevated first floors. -RPR_PCK_FCM_NAM,Commercial -RPR_PCK_FCM_DES,"Commercial, office, educational and industrial buildings with higher floor heights." -RPR_PCK_FWH_NAM,Warehouse -RPR_PCK_FWH_DES,"Retail, commercial and industrial warehouses." -RPR_PCK_FHB_NAM,High-bay warehouse -RPR_PCK_FHB_DES,High-bay industrial and commercial warehouses. -RPR_PCK_FEX_NAM,Primary industry -RPR_PCK_FEX_DES,Industrial extractors and processors -RPR_PCK_SVE_NAM,Vanilla elementary -RPR_PCK_SVE_DES,Scaling according to the vanilla elementary school -RPR_PCK_SVH_NAM,Vanilla high school -RPR_PCK_SVH_DES,Scaling according to the vanilla high school -RPR_PCK_SVC_NAM,Vanilla community -RPR_PCK_SVC_DES,Scaling according to the vanilla community school -RPR_PCK_SVA_NAM,Vanilla art school -RPR_PCK_SVA_DES,Scaling according to the vanilla art school -RPR_CAT_ALL,All categories -RPR_CAT_RES,Residential -RPR_CAT_RLO,Residential low -RPR_CAT_RHI,Residential high -RPR_CAT_COM,Commercial -RPR_CAT_CLO,Commercial low -RPR_CAT_CHI,Commercial high -RPR_CAT_OFF,Office -RPR_CAT_GEN,Generic -RPR_CAT_ITC,IT Cluster -RPR_CAT_IND,Industrial -RPR_CAT_FAR,Farming -RPR_CAT_FOR,Forestry -RPR_CAT_OIL,Oil -RPR_CAT_ORE,Ore -RPR_CAT_EXT,Extractor -RPR_CAT_PRO,Processor -RPR_CAT_TOU,Tourism -RPR_CAT_LEI,Leisure -RPR_CAT_ORG,Organic and local produce -RPR_CAT_ECO,Eco shops -RPR_CAT_SSH,Self-sufficient homes -RPR_CAT_ERL,Eco residential low -RPR_CAT_ERH,Eco residential high -RPR_CAT_SIN,Specialized industry -RPR_CAT_SCH,Schools -RPR_FIL_NAME,Name: -RPR_FIL_SET,Filter by settings (see tooltips) -RPR_EDT_POP,Override population -RPR_EDT_FLR,Override floors -RPR_FTR_ANY,Has custom override and/or non-default calculation pack -RPR_FTR_OVR,Has population and/or floor override -RPR_FTR_NDC,Has non-default calculation pack and/or custom population multiplier -RPR_CUS_TITLE,Custom settings -RPR_CUS_ADD,Add custom settings -RPR_CUS_ADD_TIP,Adds (or updates) a custom setting for the selected building -RPR_CUS_DEL,Delete custom settings -RPR_CUS_DEL_TIP,Removes the custom setting from the selected building -RPR_CUS_UPD,Update custom settings -RPR_ERR_ZERO,ERROR: value needs to be greater than zero -RPR_ERR_INV,ERROR: invalid value -RPR_LBL_HOM,Homes: -RPR_LBL_JOB,Jobs: -RPR_LBL_STU,Students: -RPR_LBL_OFF,First floor height -RPR_LBL_OFH,Other floor height -RPR_PRV_SFL,Show calculated floors -RPR_CAL_MOD,Mod calculations -RPR_CAL_DEN,Population density -RPR_CAL_BFL,Building floors -RPR_CAL_FOV,Using custom override -RPR_CAL_FLG,Floor calculation selection is not available when using legacy calculations -RPR_CAL_SAT,Save and apply to: -RPR_CAL_NBD,New buildings only -RPR_CAL_ABD,All buildings -RPR_CAL_VOL_FLR,Floors -RPR_CAL_VOL_FLR_TIP,The number of floors (stories) for this building calculated using the above settings. -RPR_CAL_VOL_TFA,Total floor area -RPR_CAL_VOL_TFA_TIP,The total floor area for this building calculated using the above settings. -RPR_CAL_VOL_UTS,Total units -RPR_CAL_VOL_UTS_TIP,The total population (households or workplaces) for this building calculated using the above settings. -RPR_CAL_VOL_VIS,Visitor count -RPR_CAL_VOL_VIS_TIP,The maximum number of simultaneous customers supported by this building. -RPR_CAL_VOL_PRD,Production rate -RPR_CAL_VOL_PRD_TIP,This building's maximum rate of production. -RPR_CAL_VOL_FLH,Floor height -RPR_CAL_VOL_FLH_TIP,"The typical floor-to-floor height, in meters, of each story in this building (the first floor has additional options below)." -RPR_CAL_VOL_FMN,First floor height min -RPR_CAL_VOL_FMN_TIP,The minimum building height required for an area of the building to count as having at least one story. -RPR_CAL_VOL_FMX,First floor extra height -RPR_CAL_VOL_FMX_TIP,The first floor height will be extended upwards by this many meters (on top of the typical floor height above) before additional floors are added above. -RPR_CAL_VOL_IGF,Ignore first floor -RPR_CAL_VOL_IGF_TIP,"If this is set, the first floor of the building is treated as empty space (e.g. a lobby or foyer area) with no households or workers." -RPR_CAL_VOL_EPC,Empty area (%) -RPR_CAL_VOL_EPC_TIP,This percentage of each floor area will be treated as empty area and will not be included when calculating households or worker counts. -RPR_CAL_VOL_EMP,Empty area (fixed) -RPR_CAL_VOL_EMP_TIP,A fixed amount of space that will be deducted from each floor area before households or workers are calculated (treated as empty area). -RPR_CAL_VOL_FXP,Fixed population? -RPR_CAL_VOL_FXP_TIP,"If this is set, a fixed figure for household or worker counts will be used, regardless of building size (e.g. one household per building)." -RPR_CAL_VOL_UNI,Units -RPR_CAL_VOL_HOU,Households -RPR_CAL_VOL_WOR,Workplaces -RPR_CAL_VOL_STU,Students -RPR_CAL_VOL_UNI_TIP,Fixed number of households or workers per building. -RPR_CAL_VOL_APU,Area per unit -RPR_CAL_VOL_APU_TIP,The area (in square meters) required for each household or worker (after empty area has been deducted). -RPR_CAL_VOL_MFU,Multi-floor units -RPR_CAL_VOL_MFU_TIP,"If this is not set, then household or worker counts will be calculated for each individual floor in the building. If this is set, only the building total will be used to calculate household or worker counts." -RPR_CAL_VOL_ARA,area -RPR_CAL_UNI_HOU,homes -RPR_CAL_UNI_WOR,workers -RPR_CAL_UNI_STU,students -RPR_CAL_HOM_CALC,Calculated homes: -RPR_CAL_HOM_CUST,Customised homes: -RPR_CAL_HOM_APPL,Applied homes: -RPR_CAL_JOB_CALC,Calculated jobs: -RPR_CAL_JOB_CUST,Customised jobs: -RPR_CAL_JOB_APPL,Applied jobs: -RPR_CAL_BLD_X,Building width (m): -RPR_CAL_BLD_Z,Building length (m): -RPR_CAL_LOT_X,Lot width (squares): -RPR_CAL_LOT_Z,Lot length (squares): -RPR_CAL_BLD_Y,Scaffolding height (m): -RPR_CAL_BLD_M2,m2 per person: -RPR_CAL_FLR_Y,Floor height (m): -RPR_CAL_FLR_M,Floor mod -RPR_CAL_FLR,Calculated floors: -RPR_CAL_M2,Calculated area: -RPR_CAL_OVR,(Overridden) -RPR_CAL_OVM,Population calculation is overriden by manual setting -RPR_CAL_RICO,Ploppable RICO Revisited is controlling the figures for this building. -RPR_CAL_CAP_OVR,Custom capacity multiplier -RPR_CAL_CAP_DEF,Default multiplier: -RPR_CAL_SCH_PRO,Staff and cost scaling -RPR_CAL_SCH_WKR,School workers -RPR_CAL_SCH_WKR_TIP,The number and level of workplaces available at this school calculated using the above settings -RPR_CAL_SCH_CST,Cost/maintenance -RPR_CAL_SCH_CST_TIP,The school's construction cost and weekly maintenance cost calculated using the above settings -RPR_TIT_RDF,Residential calculation defaults -RPR_TIT_CDF,Commercial calculation defaults -RPR_TIT_IDF,Industrial calculation defaults -RPR_TIT_EDF,Extractor calculation defaults -RPR_TIT_ODF,Office calculation defaults -RPR_TIT_SDF,School calculation defaults -RPR_TIT_CGO,Commercial goods settings -RPR_TIT_IGO,Industrial production settings -RPR_TIT_EGO,Extractor production settings -RPR_TIT_OGO,Office production settings -RPR_OPT_MOD,Mod settings -RPR_OPT_KEY,Hotkey for building options screen -RPR_OPT_PRS,Press any key -RPR_OPT_LEG,Legacy -RPR_OPT_CON,Consumption -RPR_OPT_LVL,Level -RPR_OPT_APH,Area per household (m2) -RPR_OPT_APW,Area per worker (m2) -RPR_OPT_FLR,Floor height (m) -RPR_OPT_POW,Power -RPR_OPT_WAT,Water -RPR_OPT_SEW,Sewage -RPR_OPT_GAR,Garbage -RPR_OPT_POL,Pollution -RPR_OPT_NOI,Noise -RPR_OPT_MAI,Mail -RPR_OPT_PRO,Production -RPR_OPT_WEA,Wealth -RPR_OPT_RTD,Reset to defaults -RPR_OPT_RTS,Revert to saved -RPR_OPT_SAA,Save and apply -RPR_OPT_SCH,Schools -RPR_OPT_SEN,Enable realistic school student capacities -RPR_OPT_SEJ,"Enable realistic school properties (jobs, costs) - requires restart" -RPR_OPT_SDM,Default school capacity multiplier -RPR_OPT_JB0,Uneducated jobs -RPR_OPT_JB1,Educated jobs -RPR_OPT_JB2,Well-educated jobs -RPR_OPT_JB3,Highly-educated jobs -RPR_OPT_SHE,Elementary schools -RPR_OPT_SHH,High schools -RPR_OPT_SJB,Base jobs -RPR_OPT_SJX,Extra job per # students -RPR_OPT_CRI,Crime -RPR_OPT_CML,Crime multiplier -RPR_OPT_CML_TIP,The base crime accumulation for the city will be multiplied by this amount -RPR_OPT_POP,Population calculation packs -RPR_OPT_STO,Floor calculation packs -RPR_OPT_DEF,Default calculation packs -RPR_OPT_PSI,"Production, sales and inventory settings" -RPR_OPT_EDT_NAM,Pack name -RPR_OPT_SVC,Service -RPR_OPT_CPK,Calculation pack -RPR_OPT_NEW,Add new -RPR_OPT_DEL,Delete pack -RPR_OPT_NPK,New pack -RPR_OPT_MEA,Display figures in US Customary units (instead of metric) -RPR_OPT_LDT,Enable detailed debugging logging -RPR_DEF_DMR,Default residential building calculation mode: -RPR_DEF_DMC,Default commercial building calculation mode: -RPR_DEF_DMI,Default industrial building calculation mode: -RPR_DEF_DMO,Default office building calculation mode: -RPR_DEF_LTS,This save -RPR_DEF_LAS,New saves -RPR_DEF_VIS,Customer (visitor) capacity -RPR_DEF_VIS_TIP,"The basis for calculating the number of customers this building can handle at once. The old method was to use the size of the building's lot, and the new method is to use the number of workplaces in the building." -RPR_DEF_VNE,New method (population) -RPR_DEF_VOL,Old method (lot size) -RPR_DEF_VMU,Customer count multiplier -RPR_DEF_VMU_TIP,The calculated customer capacity will be multiplied by this amount -RPR_DEF_CGM,Sales multiplier -RPR_DEF_CGM_TIP,"The amount of goods sold by this building per customer will be multiplied by this amount; reduced rates will mean fewer goods are required, but tax revenue will also be reduced" -RPR_DEF_IDC,Inventory demand cap -RPR_DEF_IDC_TIP,Sets the maximum building inventory level that will trigger the ordering of more goods (does not affect buildings with calculated inventory order trigger levels that are already lower than this cap). -RPR_DEF_PMD,Calculation mode -RPR_DEF_PMD_TIP,"The basis for calculating the production rate of this building. The old method was to use the size of the building's lot, and the new method is to use the number of workplaces in the building." -RPR_DEF_PRD,Production multiplier -RPR_DEF_PRD_TIP,The amount of goods produced by this building will be muliplied by this amount -TRN_CHOICE,Set language -TRN_SYS,Use system language -MES_CLS,Close -MES_DSA,Don't show again -MES_PAGE,Please refer to the mod's Steam Workshop page for further information. -RPR_OLD_0,This savefile has not been saved with Realistic Population 2. -RPR_OLD_1,Please be aware that applying different population calculations to an existing city can temporarily disrupt city balance. -RPR_OLD_3,"Note that in any case, existing residential buildings will retain their existing capacities and households and will NOT be affected, and workers with existing jobs will NOT be immediately displaced from those jobs, so there will be no immediate impact on your city's population." -RPR_OLD_4,"Choose new calculations to immediately apply the mod's standard calculations." -RPR_OLD_5,"Choose vanilla calculations to continue using the game's default calculations. You can change to the mod's calculations later." -RPR_OLD_6,"Choose legacy calculations to use the calculations used by earlier versions of this mod (the Realistic Population and Consumption mod and Realistic Population Revisited version 1). Only choose this if your savefile used one of these earlier versions." -RPR_OLD_VAN,Vanilla calculations -RPR_OLD_LEG,Legacy calculations -RPR_OLD_NEW,New calculations -RPR_ERR_FAT,This means that Realistic Population 2 is not able to operate properly and has shut down. -RPR_ERR_HAR,Realistic Population 2 was unable to complete its required Harmony patches. -ERR_HAR0,Harmony patching error! -ERR_HAR1,Possible causes of this problem include: -ERR_HAR2,The required Harmony 2 mod dependency was not installed -ERR_HAR3,An old and/or broken mod is preventing Harmony 2 from operating properly -ERR_CON0,Mod conflict detected! -ERR_CON1,The conflicting mods are: -RPR_ERR_CON0,Realistic Population 2 detected a conflict with at least one other mod. -RPR_ERR_CON1,These mods must be removed before Realistic Population 2 can operate. -RPR_200_0,Renaming from 'Realistic Population Revisited' to 'Realistic Population 2' to reflect the extent of the changes -RPR_200_1,"MAJOR NEW FEATURE: new building population calculations based on actual building shapes and sizes with realistically calculated floorspace, with the option to choose between the new or old ('legacy') calculations" -RPR_200_2,"MAJOR NEW FEATURE: separate calculation presets for building floorspace and population, along with optional manual overrides for either or both" -RPR_200_3,MAJOR NEW FEATURE: selectable configuration presets for each building service type -RPR_200_4,"MAJOR NEW FEATURE: optional realistic student capacity calculations for elementary and high schools, with configurable multiplier for when realistically-sized school buildings aren't available" -RPR_200_5,MAJOR NEW FEATURE: adjustable crime rate -RPR_200_6,Upgrade to Harmony 2 (boformer's Harmony 2 mod required) -RPR_200_7,Various enhancements to the building details panel -RPR_200_8,Improved mod conflict detection and notification -RPR_200_9,"Significant overhaul, cleanup and restructure of the mod's codebase" -RPR_201_0,"Add option to use US customary measures (feet and square feet) in place of metric measurements" \ No newline at end of file +"LANGUAGE","English" +"RPR_NAME","Realistic Population 2" +"RPR_DESC","More realistic building populations (based on building size) and utility needs." +"RPR_REALPOP","Realistic Population" +"RPR_INF_VIS","visitors" +"RPR_PCK_DEF"," (default)" +"RPR_PCK_NAM","Calculations" +"RPR_PCK_VAN_NAM","Vanilla (base game)" +"RPR_PCK_VAN_DES","Calculations as used by the base (unmodded) game." +"RPR_PCK_LEG_NAM","Legacy WG calculations" +"RPR_PCK_LEG_DES","Old calculations used by versions 1.x of this mod and Whitefang Greytail's original Realistic Population and Consumption mod." +"RPR_PCK_RLS_NAM","Single dwellings" +"RPR_PCK_RLS_DES","One household per building." +"RPR_PCK_RLD_NAM","Duplexes" +"RPR_PCK_RLD_DES","Two households per building." +"RPR_PCK_RUH_NAM","US highrise" +"RPR_PCK_RUH_DES","Average US apartments and condos." +"RPR_PCK_REM_NAM","European apartments (modern)" +"RPR_PCK_REM_DES","European mid-rise and wall-to-wall apartments (medium density; modern buildings)." +"RPR_PCK_REO_NAM","European apartments (older)" +"RPR_PCK_REO_DES","European mid-rise and wall-to-wall apartments (high density; older buildings)." +"RPR_PCK_CUS_NAM","US retail" +"RPR_PCK_CUS_DES","General commercial retail density typical of USA." +"RPR_PCK_CUK_NAM","UK retail" +"RPR_PCK_CUK_DES","General commercial retail density typical of UK." +"RPR_PCK_CRW_NAM","Superstores" +"RPR_PCK_CRW_DES","Non-food superstores and retail warehouses." +"RPR_PCK_THT_NAM","Hotels" +"RPR_PCK_THT_DES","Hotels and motels." +"RPR_PCK_LFD_NAM","Food service" +"RPR_PCK_LFD_DES","Restaurants and cafes." +"RPR_PCK_LEN_NAM","Entertainment" +"RPR_PCK_LEN_DES","Amusement, fitness, and entertainment centers." +"RPR_PCK_LCN_NAM","Cinemas" +"RPR_PCK_LCN_DES","Cinemas and theaters." +"RPR_PCK_ILG_NAM","Light industry" +"RPR_PCK_ILG_DES","Light industries and business parks." +"RPR_PCK_IMN_NAM","Manufacturing" +"RPR_PCK_IMN_DES","Industrial manufacturing." +"RPR_PCK_IWL_NAM","Small warehouses" +"RPR_PCK_IWL_DES","Warehouses for local distribution - lots of storage and low employment density." +"RPR_PCK_IWN_NAM","Large warehouses" +"RPR_PCK_IWN_DES","Warehouses for national distribution - lots of storage and very low employment density." +"RPR_PCK_OCP_NAM","Corporate offices" +"RPR_PCK_OCP_DES","Corporate, professional services, and public sector offices." +"RPR_PCK_OFN_NAM","Finance offices" +"RPR_PCK_OFN_DES","Financial offices; higher density than typical corporate offices" +"RPR_PCK_OCS_NAM","Call centers" +"RPR_PCK_OCS_DES","Call centers and other tightly-packed offices" +"RPR_PCK_ODT_NAM","Datacenters" +"RPR_PCK_ODT_DES","Datacenters with co-location facilities" +"RPR_PCK_SSB_NAM","Surburban school" +"RPR_PCK_SSB_DES","Suburban schools" +"RPR_PCK_SML_NAM","MN low" +"RPR_PCK_SML_DES","Minnesota Dept of Education guide - lowest density" +"RPR_PCK_SMM_NAM","MN medium" +"RPR_PCK_SMM_DES","Minnesota Dept of Education guide - medium density" +"RPR_PCK_SMH_NAM","MN high" +"RPR_PCK_SMH_DES","Minnesota Dept of Education guide - highest density" +"RPR_PCK_SUK_NAM","UK standard" +"RPR_PCK_SUK_DES","Standard planning densities for the UK." +"RPR_PCK_FDF_NAM","Generic" +"RPR_PCK_FDF_DES","Standard 3m floor height." +"RPR_PCK_FHO_NAM","Standalone houses" +"RPR_PCK_FHO_DES","Detached residential dwellings." +"RPR_PCK_FDL_NAM","Lobbies" +"RPR_PCK_FDL_DES","Buildings with lobbies and/or elevated first floors." +"RPR_PCK_FCM_NAM","Commercial" +"RPR_PCK_FCM_DES","Commercial, office, educational and industrial buildings with higher floor heights." +"RPR_PCK_FWH_NAM","Warehouse" +"RPR_PCK_FWH_DES","Retail, commercial and industrial warehouses." +"RPR_PCK_FHB_NAM","High-bay warehouse" +"RPR_PCK_FHB_DES","High-bay industrial and commercial warehouses." +"RPR_PCK_FEX_NAM","Primary industry" +"RPR_PCK_FEX_DES","Industrial extractors and processors" +"RPR_PCK_SVE_NAM","Vanilla elementary" +"RPR_PCK_SVE_DES","Scaling according to the vanilla elementary school" +"RPR_PCK_SVH_NAM","Vanilla high school" +"RPR_PCK_SVH_DES","Scaling according to the vanilla high school" +"RPR_PCK_SVC_NAM","Vanilla community" +"RPR_PCK_SVC_DES","Scaling according to the vanilla community school" +"RPR_PCK_SVA_NAM","Vanilla art school" +"RPR_PCK_SVA_DES","Scaling according to the vanilla art school" +"RPR_CAT_ALL","All categories" +"RPR_CAT_RES","Residential" +"RPR_CAT_RLO","Residential low" +"RPR_CAT_RHI","Residential high" +"RPR_CAT_COM","Commercial" +"RPR_CAT_CLO","Commercial low" +"RPR_CAT_CHI","Commercial high" +"RPR_CAT_OFF","Office" +"RPR_CAT_GEN","Generic" +"RPR_CAT_ITC","IT Cluster" +"RPR_CAT_IND","Industrial" +"RPR_CAT_FAR","Farming" +"RPR_CAT_FOR","Forestry" +"RPR_CAT_OIL","Oil" +"RPR_CAT_ORE","Ore" +"RPR_CAT_EXT","Extractor" +"RPR_CAT_PRO","Processor" +"RPR_CAT_TOU","Tourism" +"RPR_CAT_LEI","Leisure" +"RPR_CAT_ORG","Organic and local produce" +"RPR_CAT_ECO","Eco shops" +"RPR_CAT_SSH","Self-sufficient homes" +"RPR_CAT_ERL","Eco residential low" +"RPR_CAT_ERH","Eco residential high" +"RPR_CAT_SIN","Specialized industry" +"RPR_CAT_SCH","Schools" +"RPR_FIL_NAME","Name:" +"RPR_FIL_SET","Filter by settings (see tooltips)" +"RPR_EDT_POP","Override population" +"RPR_EDT_FLR","Override floors" +"RPR_FTR_ANY","Has custom override and/or non-default calculation pack" +"RPR_FTR_OVR","Has population and/or floor override" +"RPR_FTR_NDC","Has non-default calculation pack and/or custom population multiplier" +"RPR_CUS_TITLE","Custom settings" +"RPR_CUS_ADD","Add custom settings" +"RPR_CUS_ADD_TIP","Adds (or updates) a custom setting for the selected building" +"RPR_CUS_DEL","Delete custom settings" +"RPR_CUS_DEL_TIP","Removes the custom setting from the selected building" +"RPR_CUS_UPD","Update custom settings" +"RPR_ERR_ZERO","ERROR: value needs to be greater than zero" +"RPR_ERR_INV","ERROR: invalid value" +"RPR_LBL_HOM","Homes:" +"RPR_LBL_JOB","Jobs:" +"RPR_LBL_STU","Students:" +"RPR_LBL_OFF","First floor height" +"RPR_LBL_OFH","Other floor height" +"RPR_PRV_SFL","Show calculated floors" +"RPR_CAL_MOD","Mod calculations" +"RPR_CAL_DEN","Population density" +"RPR_CAL_BFL","Building floors" +"RPR_CAL_FOV","Using custom override" +"RPR_CAL_FLG","Floor calculation selection is not available when using legacy calculations" +"RPR_CAL_SAT","Save and apply to:" +"RPR_CAL_NBD","New buildings only" +"RPR_CAL_ABD","All buildings" +"RPR_CAL_VOL_FLR","Floors" +"RPR_CAL_VOL_FLR_TIP","The number of floors (stories) for this building calculated using the above settings." +"RPR_CAL_VOL_TFA","Total floor area" +"RPR_CAL_VOL_TFA_TIP","The total floor area for this building calculated using the above settings." +"RPR_CAL_VOL_UTS","Total units" +"RPR_CAL_VOL_UTS_TIP","The total population (households or workplaces) for this building calculated using the above settings." +"RPR_CAL_VOL_VIS","Visitor count" +"RPR_CAL_VOL_VIS_TIP","The maximum number of simultaneous customers supported by this building." +"RPR_CAL_VOL_PRD","Production rate" +"RPR_CAL_VOL_PRD_TIP","This building's maximum rate of production." +"RPR_CAL_VOL_FLH","Floor height" +"RPR_CAL_VOL_FLH_TIP","The typical floor-to-floor height, in meters, of each story in this building (the first floor has additional options below)." +"RPR_CAL_VOL_FMN","First floor height min" +"RPR_CAL_VOL_FMN_TIP","The minimum building height required for an area of the building to count as having at least one story." +"RPR_CAL_VOL_FMX","First floor extra height" +"RPR_CAL_VOL_FMX_TIP","The first floor height will be extended upwards by this many meters (on top of the typical floor height above) before additional floors are added above." +"RPR_CAL_VOL_IGF","Ignore first floor" +"RPR_CAL_VOL_IGF_TIP","If this is set, the first floor of the building is treated as empty space (e.g. a lobby or foyer area) with no households or workers." +"RPR_CAL_VOL_EPC","Empty area (%)" +"RPR_CAL_VOL_EPC_TIP","This percentage of each floor area will be treated as empty area and will not be included when calculating households or worker counts." +"RPR_CAL_VOL_EMP","Empty area (fixed)" +"RPR_CAL_VOL_EMP_TIP","A fixed amount of space that will be deducted from each floor area before households or workers are calculated (treated as empty area)." +"RPR_CAL_VOL_FXP","Fixed population?" +"RPR_CAL_VOL_FXP_TIP","If this is set, a fixed figure for household or worker counts will be used, regardless of building size (e.g. one household per building)." +"RPR_CAL_VOL_UNI","Units" +"RPR_CAL_VOL_HOU","Households" +"RPR_CAL_VOL_WOR","Workplaces" +"RPR_CAL_VOL_STU","Students" +"RPR_CAL_VOL_UNI_TIP","Fixed number of households or workers per building." +"RPR_CAL_VOL_APU","Area per unit" +"RPR_CAL_VOL_APU_TIP","The area (in square meters) required for each household or worker (after empty area has been deducted)." +"RPR_CAL_VOL_MFU","Multi-floor units" +"RPR_CAL_VOL_MFU_TIP","If this is not set, then household or worker counts will be calculated for each individual floor in the building. If this is set, only the building total will be used to calculate household or worker counts." +"RPR_CAL_VOL_ARA","area" +"RPR_CAL_UNI_HOU","homes" +"RPR_CAL_UNI_WOR","workers" +"RPR_CAL_UNI_STU","students" +"RPR_CAL_HOM_CALC","Calculated homes:" +"RPR_CAL_HOM_CUST","Customised homes:" +"RPR_CAL_HOM_APPL","Applied homes:" +"RPR_CAL_JOB_CALC","Calculated jobs:" +"RPR_CAL_JOB_CUST","Customised jobs:" +"RPR_CAL_JOB_APPL","Applied jobs:" +"RPR_CAL_BLD_X","Building width (m):" +"RPR_CAL_BLD_Z","Building length (m):" +"RPR_CAL_LOT_X","Lot width (squares):" +"RPR_CAL_LOT_Z","Lot length (squares):" +"RPR_CAL_BLD_Y","Scaffolding height (m):" +"RPR_CAL_BLD_M2","m2 per person:" +"RPR_CAL_FLR_Y","Floor height (m):" +"RPR_CAL_FLR_M","Floor mod" +"RPR_CAL_FLR","Calculated floors:" +"RPR_CAL_M2","Calculated area:" +"RPR_CAL_OVR","(Overridden)" +"RPR_CAL_OVM","Population calculation is overriden by manual setting" +"RPR_CAL_RICO","Ploppable RICO Revisited is controlling the figures for this building." +"RPR_CAL_CAP_OVR","Custom capacity multiplier" +"RPR_CAL_CAP_DEF","Default multiplier:" +"RPR_CAL_SCH_PRO","Staff and cost scaling" +"RPR_CAL_SCH_WKR","School workers" +"RPR_CAL_SCH_WKR_TIP","The number and level of workplaces available at this school calculated using the above settings" +"RPR_CAL_SCH_CST","Cost/maintenance" +"RPR_CAL_SCH_CST_TIP","The school's construction cost and weekly maintenance cost calculated using the above settings" +"RPR_TIT_RDF","Residential calculation defaults" +"RPR_TIT_CDF","Commercial calculation defaults" +"RPR_TIT_IDF","Industrial calculation defaults" +"RPR_TIT_EDF","Extractor calculation defaults" +"RPR_TIT_ODF","Office calculation defaults" +"RPR_TIT_SDF","School calculation defaults" +"RPR_TIT_CGO","Commercial goods settings" +"RPR_TIT_IGO","Industrial production settings" +"RPR_TIT_EGO","Extractor production settings" +"RPR_TIT_OGO","Office production settings" +"RPR_OPT_MOD","Mod settings" +"RPR_OPT_KEY","Hotkey for building options screen" +"RPR_OPT_PRS","Press any key" +"RPR_OPT_LEG","Legacy" +"RPR_OPT_CON","Consumption" +"RPR_OPT_LVL","Level" +"RPR_OPT_APH","Area per household (m2)" +"RPR_OPT_APW","Area per worker (m2)" +"RPR_OPT_FLR","Floor height (m)" +"RPR_OPT_POW","Power" +"RPR_OPT_WAT","Water" +"RPR_OPT_SEW","Sewage" +"RPR_OPT_GAR","Garbage" +"RPR_OPT_POL","Pollution" +"RPR_OPT_NOI","Noise" +"RPR_OPT_MAI","Mail" +"RPR_OPT_PRO","Production" +"RPR_OPT_WEA","Wealth" +"RPR_OPT_RTD","Reset to defaults" +"RPR_OPT_RTS","Revert to saved" +"RPR_OPT_SAA","Save and apply" +"RPR_OPT_SCH","Schools" +"RPR_OPT_SEN","Enable realistic school student capacities" +"RPR_OPT_SEJ","Enable realistic school properties (jobs, costs) - requires restart" +"RPR_OPT_SDM","Default school capacity multiplier" +"RPR_OPT_JB0","Uneducated jobs" +"RPR_OPT_JB1","Educated jobs" +"RPR_OPT_JB2","Well-educated jobs" +"RPR_OPT_JB3","Highly-educated jobs" +"RPR_OPT_SHE","Elementary schools" +"RPR_OPT_SHH","High schools" +"RPR_OPT_SJB","Base jobs" +"RPR_OPT_SJX","Extra job per # students" +"RPR_OPT_CRI","Crime" +"RPR_OPT_CML","Crime multiplier" +"RPR_OPT_CML_TIP","The base crime accumulation for the city will be multiplied by this amount" +"RPR_OPT_POP","Population calculation packs" +"RPR_OPT_STO","Floor calculation packs" +"RPR_OPT_DEF","Default calculation packs" +"RPR_OPT_PSI","Production, sales and inventory settings" +"RPR_OPT_EDT_NAM","Pack name" +"RPR_OPT_SVC","Service" +"RPR_OPT_CPK","Calculation pack" +"RPR_OPT_NEW","Add new" +"RPR_OPT_DEL","Delete pack" +"RPR_OPT_NPK","New pack" +"RPR_OPT_MEA","Display figures in US Customary units (instead of metric)" +"RPR_OPT_LDT","Enable detailed debugging logging" +"RPR_DEF_DMR","Default residential building calculation mode:" +"RPR_DEF_DMC","Default commercial building calculation mode:" +"RPR_DEF_DMI","Default industrial building calculation mode:" +"RPR_DEF_DMO","Default office building calculation mode:" +"RPR_DEF_LTS","This save" +"RPR_DEF_LAS","New saves" +"RPR_DEF_VIS","Customer (visitor) capacity" +"RPR_DEF_VIS_TIP","The basis for calculating the number of customers this building can handle at once. The old method was to use the size of the building's lot, and the new method is to use the number of workplaces in the building." +"RPR_DEF_VNE","New method (population)" +"RPR_DEF_VOL","Old method (lot size)" +"RPR_DEF_VMU","Customer count multiplier" +"RPR_DEF_VMU_TIP","The calculated customer capacity will be multiplied by this amount" +"RPR_DEF_CGM","Sales multiplier" +"RPR_DEF_CGM_TIP","The amount of goods sold by this building per customer will be multiplied by this amount; reduced rates will mean fewer goods are required, but tax revenue will also be reduced" +"RPR_DEF_IDC","Inventory demand cap" +"RPR_DEF_IDC_TIP","Sets the maximum building inventory level that will trigger the ordering of more goods (does not affect buildings with calculated inventory order trigger levels that are already lower than this cap)." +"RPR_DEF_PMD","Calculation mode" +"RPR_DEF_PMD_TIP","The basis for calculating the production rate of this building. The old method was to use the size of the building's lot, and the new method is to use the number of workplaces in the building." +"RPR_DEF_PRD","Production multiplier" +"RPR_DEF_PRD_TIP","The amount of goods produced by this building will be muliplied by this amount" +"TRN_CHOICE","Set language" +"TRN_SYS","Use system language" +"MES_CLS","Close" +"MES_DSA","Don't show again" +"MES_PAGE","Please refer to the mod's Steam Workshop page for further information." +"RPR_OLD_0","This savefile has not been saved with Realistic Population 2." +"RPR_OLD_1","Please be aware that applying different population calculations to an existing city can temporarily disrupt city balance." +"RPR_OLD_3","Note that in any case, existing residential buildings will retain their existing capacities and households and will NOT be affected, and workers with existing jobs will NOT be immediately displaced from those jobs, so there will be no immediate impact on your city's population." +"RPR_OLD_4","Choose new calculations to immediately apply the mod's standard calculations." +"RPR_OLD_5","Choose vanilla calculations to continue using the game's default calculations. You can change to the mod's calculations later." +"RPR_OLD_6","Choose legacy calculations to use the calculations used by earlier versions of this mod (the Realistic Population and Consumption mod and Realistic Population Revisited version 1). Only choose this if your savefile used one of these earlier versions." +"RPR_OLD_VAN","Vanilla calculations" +"RPR_OLD_LEG","Legacy calculations" +"RPR_OLD_NEW","New calculations" +"RPR_ERR_FAT","This means that Realistic Population 2 is not able to operate properly and has shut down." +"RPR_ERR_HAR","Realistic Population 2 was unable to complete its required Harmony patches." +"ERR_HAR0","Harmony patching error!" +"ERR_HAR1","Possible causes of this problem include:" +"ERR_HAR2","The required Harmony 2 mod dependency was not installed" +"ERR_HAR3","An old and/or broken mod is preventing Harmony 2 from operating properly" +"ERR_CON0","Mod conflict detected!" +"ERR_CON1","The conflicting mods are:" +"RPR_ERR_CON0","Realistic Population 2 detected a conflict with at least one other mod." +"RPR_ERR_CON1","These mods must be removed before Realistic Population 2 can operate." +"RPR_200_0","Renaming from 'Realistic Population Revisited' to 'Realistic Population 2' to reflect the extent of the changes" +"RPR_200_1","MAJOR NEW FEATURE: new building population calculations based on actual building shapes and sizes with realistically calculated floorspace, with the option to choose between the new or old ('legacy') calculations" +"RPR_200_2","MAJOR NEW FEATURE: separate calculation presets for building floorspace and population, along with optional manual overrides for either or both" +"RPR_200_3","MAJOR NEW FEATURE: selectable configuration presets for each building service type" +"RPR_200_4","MAJOR NEW FEATURE: optional realistic student capacity calculations for elementary and high schools, with configurable multiplier for when realistically-sized school buildings aren't available" +"RPR_200_5","MAJOR NEW FEATURE: adjustable crime rate" +"RPR_200_6","Upgrade to Harmony 2 (boformer's Harmony 2 mod required)" +"RPR_200_7","Various enhancements to the building details panel" +"RPR_200_8","Improved mod conflict detection and notification" +"RPR_200_9","Significant overhaul, cleanup and restructure of the mod's codebase" +"RPR_201_0","Add option to use US customary measures (feet and square feet) in place of metric measurements" \ No newline at end of file diff --git a/Translations/es-ES.csv b/Translations/es-ES.csv index 44b6a68..863a857 100644 --- a/Translations/es-ES.csv +++ b/Translations/es-ES.csv @@ -1,5 +1,4 @@ -"CODE","es" -"NAME","Español" +"LANGUAGE","Español" "RPR_NAME","Realistic Population 2" "RPR_DESC","Poblaciones más realistas en los edificios (según el tamaño del edificio) y necesidades de servicios." "RPR_REALPOP","Realistic Population" diff --git a/Translations/fr-FR.csv b/Translations/fr-FR.csv index 0568036..b052d92 100644 --- a/Translations/fr-FR.csv +++ b/Translations/fr-FR.csv @@ -1,5 +1,4 @@ -"CODE","fr" -"NAME","Français" +"LANGUAGE","Français" "RPR_NAME","Realistic Population 2" "RPR_DESC","Des populations de bâtiments plus réalistes (basées sur la taille des bâtiments) et les besoins en services publics." "RPR_REALPOP","Realistic Population" diff --git a/Translations/ja-JP.csv b/Translations/ja-JP.csv index 1c3e674..d70630b 100644 --- a/Translations/ja-JP.csv +++ b/Translations/ja-JP.csv @@ -1,5 +1,4 @@ -"CODE","ja" -"NAME","日本語" +"LANGUAGE","日本語" "RPR_NAME","Realistic Population 2" "RPR_DESC","建物のサイズを基準にして、住民や公共施設の需要をより現実的に調整します" "RPR_REALPOP","Realistic Population" diff --git a/Translations/nl-NL.csv b/Translations/nl-NL.csv index 8706aef..a65498b 100644 --- a/Translations/nl-NL.csv +++ b/Translations/nl-NL.csv @@ -1,5 +1,4 @@ -"CODE","nl" -"NAME","Nederlands" +"LANGUAGE","Nederlands" "RPR_NAME","Realistische bevolking 2" "RPR_DESC","Meer realistische bewoning (gebaseerd op grootte van gebouw) en nutsbehoeften." "RPR_REALPOP","Realistische bevolking" diff --git a/Translations/pt-BR.csv b/Translations/pt-BR.csv index 7bab888..b4a63eb 100644 --- a/Translations/pt-BR.csv +++ b/Translations/pt-BR.csv @@ -1,5 +1,4 @@ -"CODE","pt-BR" -"NAME","Português Brasileiro" +"LANGUAGE","Português Brasileiro" "RPR_NAME","População Realista 2" "RPR_DESC","Populações de construção mais realistas (com base no tamanho do edifício) e necessidades de utilidade." "RPR_REALPOP","População Realista" diff --git a/Translations/ru-RU.csv b/Translations/ru-RU.csv index b0bcc59..0201e91 100644 --- a/Translations/ru-RU.csv +++ b/Translations/ru-RU.csv @@ -1,5 +1,4 @@ -"CODE","ru" -"NAME","Русский" +"LANGUAGE","Русский" "RPR_NAME","Реалистичное население 2" "RPR_DESC","Более реалистичное количество жильцов (в зависимости от размера здания) и коммунальных потребностей." "RPR_REALPOP","Реалистичное население" diff --git a/Translations/zh-CN.csv b/Translations/zh-CN.csv index 8ef498e..6ccff12 100644 --- a/Translations/zh-CN.csv +++ b/Translations/zh-CN.csv @@ -1,5 +1,4 @@ -"CODE","zh" -"NAME","简体中文" +"LANGUAGE","简体中文" "RPR_NAME","真实人口2" "RPR_DESC","更真实的建筑人口(根据建筑规模)和公用设施需求" "RPR_REALPOP","真实人口" diff --git a/Translations/zh-TW.csv b/Translations/zh-TW.csv index f7f55b5..e102075 100644 --- a/Translations/zh-TW.csv +++ b/Translations/zh-TW.csv @@ -1,5 +1,4 @@ -"CODE","zh-TW" -"NAME","繁體中文" +"LANGUAGE","繁體中文" "RPR_NAME","真實人口2 (Realistic Population 2)" "RPR_DESC","更真實的建築人口(依據建築尺吋)和效用需求。" "RPR_REALPOP","真實人口"