Что такое ключ и зачем он нужен
Ключ, а точнее ключевое слово — это слово по которому будет определено какой именно текст нужно, а потом уже идет поиск по выбранному языку. Пример ключевого слова: scene_Escape_from_jail_Ethan_dialog_with_Mary_3, да, примерно такого вида будет ключ, если ваша игра будет иметь много сцен, большой сюжет. Я же предлагаю прямо сразу писать фразу на одном из языков, чаще всего английский или тот которым свободно владеет программист. Кстати, поскольку все фразы текущего языка и основного языка будут лежать в оперативке, это будет более производительная чем доставать каждый раз из файла, для больших же игр, можно немного модифицировать файл под каждую сцену.
Как же будет все устроено
В ниже описанных действия будет использован класс статический Lang, в котором и будут происходить все поиски слов/фраз. Давайте же объявим класс, подключив нужные нам библиотеки:
using UnityEngine; using System.Collections.Generic; // list and dictionary #if UNITY_EDITOR using UnityEditor; using System.IO; // created file in editor #endif public class Lang { private const string Path = "/Resources/"; // path to resources folder private const string FileName = "Language"; // file name with phrases }
И так, без стандартной библиотеки не обойтись, так как мы будем доставать файл из ресурсов, можно же загружать любым удобным способом, но так удобнее и практичнее. Системные библиотеки нужны для подключения списка, словаря и работы с файлами из редактора. Библиотека UnityEditor нужна только для обновления файла во время первой записи фразы, т.к. после быстрого перезапуска не всегда будут загружены все фразы, но с помощью данной библиотеки мы сможем решить данную проблему. Класс хранит два статических поля, это пусть и названия файла, в нашем же случае путь — это папка с ресурсами, а названия файла может быть любое.
Теперь нужно добавить списки для хранения все используемых языком и словарь.
private static int LangIndex; // variable to store the index of the current language private static List<SystemLanguage> languages = new List<SystemLanguage>(); // having languages in game private static Dictionary<string, string> Phrases = new Dictionary<string, string>(); // keys and values
Поле LangIndex будет держать индекс текущего языка относительно записи в файле. В списке languages — будут записаны все языки используемые в файле. Словарь же будет хранить все фразы на основном языке и на текущем языке.
Нужно добавить инициализацию выше описанных полей класса.
public static bool isStarting // bool for check starting { get; private set; } public static SystemLanguage language // return current language { get; private set; } #if UNITY_EDITOR public static void Starting(SystemLanguage _language, SystemLanguage default_language = SystemLanguage.English, params SystemLanguage[] _languages) // write languages without main language, it self added #else public static void Starting(SystemLanguage _language = SystemLanguage.English) // main language - only for compilation #endif { #if UNITY_EDITOR if (!File.Exists(Application.dataPath + Path + FileName + ".csv")) // if file wasn't created { File.Create(Application.dataPath + "/Resources/" + FileName + ".csv").Dispose(); // create and lose link File.WriteAllText(Application.dataPath + "/Resources/" + FileName + ".csv", SetLanguage(default_language, _languages)); // write default text with index } #endif string[] PhrasesArr = Resources.Load<TextAsset>(FileName).text.Split('\n'); // temp var for write in dicrionary string[] string_languages = PhrasesArr[0].Split(';'); // string with using languages int _length = string_languages.Length - 1; for (int i = 0; i < _length; i++) { languages.Add(SystemLanguageParse(string_languages[i])); // string language to SystemLanguage } LangIndex = FindIndexLanguage(_language); // index with current language for (int i = 0; i < PhrasesArr.Length; i++) // add keys and value { string[] temp_string = PhrasesArr[i].Split(';'); if (temp_string.Length > LangIndex) Phrases.Add(temp_string[0], temp_string[LangIndex]); else Phrases.Add(temp_string[0], temp_string[0]); } isStarting = true; }
Будет сразу использовать встроенный директивы да бы не делать лишних действий после компиляции приложения. Вызов Lang.Starting(...) должен происходит примерно таким образом:
#if !UNITY_EDITOR Lang.Starting(LANGUAGE); #else Lang.Starting(LANGUAGE, SystemLanguage.English, SystemLanguage.Russian, SystemLanguage.Ukrainian); #endif private static int FindIndexLanguage(SystemLanguage _language) // finding index or current language { int _index = languages.IndexOf(_language); if (_index == -1) // if language not found return 0; // return main language return _index; } #if UNITY_EDITOR private static void Add(string AddString) // add phrases only form editor { File.AppendAllText(Application.dataPath + "/Resources/" + FileName +".csv", AddString + "\n"); // rewrite text to file Phrases.Add(AddString, AddString); // add phrase to dicrionary AssetDatabase.Refresh(); // refresh file } #endif #if UNITY_EDITOR private static string SetLanguage(SystemLanguage default_language, params SystemLanguage[] _languages) // set first string to file { string ret_string = ""; ret_string += default_language + ";"; foreach (SystemLanguage _language in _languages) { ret_string += _language + ";"; } return ret_string + "!@#$%\n"; // for last index } #endif
Вызов при игре в редакторе должен содержать два основных параметра, это на какой язык должны переводиться фразы сейчас, и какой язык будет в качестве основного, все остальные параметры это параметры языков которые должны будут содержаться в файле, нужны эти параметры только во время первого запуска, когда файл еще не создан (и после удалять не обязательно), в ином случае, если нужно будет добавить какой-то язык, нужно скопировать все из файла, удалить файл, и заново запустить код в редакторе или самостоятельно дописать в файле.
В коде, приведенном выше, используется методSystemLanguageParse(...) который просто переводит названия языка из строкового типа в SystemLanguage (данный метод будет ниже).
Остановимся на методе добавления:
#if UNITY_EDITOR private static void Add(string AddString) // add phrases only form editor { File.AppendAllText(Application.dataPath + "/Resources/" + FileName +".csv", AddString + "\n"); // rewrite text to file Phrases.Add(AddString, AddString); // add phrase to dicrionary AssetDatabase.Refresh(); // refresh file } #endif
По скольку этот метод будет использоваться только при запуске из редактора, мы спокойно может использовать системную утилиту для перезаписи файла, а так же обновлять измененные файлы редакторе с помощью метода Refresh(). Между этими действиями просто добавляться в словарь фраза, да бы уберечь себя от повторной записи в этой же сесии.
Кстати, забыл сказать, фразы будут храниться в файле .csv, что позволит нам комфортно делать переводы фраз в Excele. Теперь нужно добавить хороший для нас метод, который позволит сменить язык:
public static void ChangeLanguage(SystemLanguage _language) // change language { string[] PhrasesArr = Resources.Load<TextAsset>(FileName).text.Split('\n'); // load all text from file LangIndex = FindIndexLanguage(_language); Phrases.Clear(); // clear dictionary with phrases for (int i = 1; i < PhrasesArr.Length; i++) { string[] temp_string = PhrasesArr[i].Split(';'); if (temp_string.Length > LangIndex) Phrases.Add(temp_string[0], temp_string[LangIndex]); else Phrases.Add(temp_string[0], temp_string[0]); } }
И так, подошли к самому главному методу, который и будет принимать фразу на основном языке, и выдавать на нужном пользователю:
public static string Phrase(string DefaultPhrase) // translate phrase, args use to formating string { #if UNITY_EDITOR if (!isStarting) // if not starting { throw new System.Exception("Forgot initialization.Use Lang.Starting(...)"); // throw exception } #endif string temp_EnglishPhrase = DefaultPhrase; // temp variable for try get value if (Phrases.TryGetValue(DefaultPhrase, out DefaultPhrase)) // if value has been found { return temp_EnglishPhrase; } #if UNITY_EDITOR Add(temp_EnglishPhrase); // add phrase if value hasn't been found #endif return temp_EnglishPhrase; }
Этот метод просто принимает фразу на основном языке, потом перебирает все что есть в словаре, находя такую фразу по ключу он выдает нам значения этого ключа, что является фразой на нужном нам языке. Использовать этот метод можно простой строкой кода:
string str = Lang.Phrase("Hello world");
Теперь в строку str попадет фраза на нужном нам языке, если вдруг он она отсутствует то попадет фраза указанная в параметрах, то есть Hello world.
Этот метод можно немного улучшить, да бы можно было принимать аргументы для заполнения:
public static string Phrase(string DefaultPhrase, params string[] args) // translate phrase, args use to formating string { #if UNITY_EDITOR if (!isStarting) // if not starting { throw new System.Exception("Forgot initialization.Use Lang.Starting(...)"); // throw exception } #endif string temp_EnglishPhrase = DefaultPhrase; // temp variable for try get value if (Phrases.TryGetValue(DefaultPhrase, out DefaultPhrase)) // if value has been found { if (args.Length == 0) return DefaultPhrase; return string.Format(DefaultPhrase, args); } #if UNITY_EDITOR Add(temp_EnglishPhrase); // add phrase if value hasn't been found #endif if (args.Length == 0) return temp_EnglishPhrase; return string.Format(temp_EnglishPhrase, args); }
Теперь этот метод можно вызвать так же как и раньше:
string str = Lang.Phrase("Hello world");
Но теперь у нашого метода есть форматированный вывод, указав через запятую параметры:
string str = Lang.Phrase("Hello {0} from {1}", "world", "habr");
Переводы фраз
Как я уже писал выше, файл использует разширения .csv что позволит делать все в екселе, но не все так просто, проблема си-шарпа и екселя в том, что они понимают кириллицу в разных кодировка, ексель понимает только кодировку UTF-8-BOM или же ту, которую не понимает наш ЯП, мы же должны использовать в нем только UTF-8, хоть юнитовский редактор будет понимать UTF-8-BOM, в коде же два одинаковых слова на разных кодировках (UTF-8 и UTF-8-BOM) будут не равны, что приведет к постоянному добавлению одинаковых слов в наш файл.
Кодировать файлы мы можем с помощью бесплатного NotePad++ скачав его с офф. сайта. Редактирования файла не будет приносить вам никаких проблема, для добавления одного слова можно воспользовать даже текстовым редактором, тем же нот-падом или даже нашей средой программирования.
using UnityEngine; using System.Collections.Generic; // list and dictionary #if UNITY_EDITOR using UnityEditor; using System.IO; // created file in editor #endif public class Lang { private const string Path = "/Resources/"; // path to resources folder private const string FileName = "Language"; // file name with phrases private static int NumberOfLanguage; // variable to store the index of the current language private static List<SystemLanguage> languages = new List<SystemLanguage>(); // having languages in game private static Dictionary<string, string> Phrases = new Dictionary<string, string>(); // keys and values private static SystemLanguage language; // current language #if UNITY_EDITOR public static void Starting(SystemLanguage _language, SystemLanguage default_language, params SystemLanguage[] _languages) // write languages without main language, it self added #else public static void Starting(SystemLanguage _language = SystemLanguage.English) // main language - only for compilation #endif { #if UNITY_EDITOR if (!File.Exists(Application.dataPath + Path + FileName + ".csv")) // if file wasn't created { File.Create(Application.dataPath + "/Resources/" + FileName + ".csv").Dispose(); // create and lose link File.WriteAllText(Application.dataPath + "/Resources/" + FileName + ".csv", SetLanguage(default_language, _languages)); // write default text with index } #endif string[] PhrasesArr = Resources.Load<TextAsset>(FileName).text.Split('\n'); // temp var for write in dicrionary string[] string_languages = PhrasesArr[0].Split(';'); // string with using languages int _length = string_languages.Length - 1; for (int i = 0; i < _length; i++) { languages.Add(SystemLanguageParse(string_languages[i])); // string language to SystemLanguage } NumberOfLanguage = FindIndexLanguage(_language); // index with current language for (int i = 0; i < PhrasesArr.Length; i++) // add keys and value { string[] temp_string = PhrasesArr[i].Split(';'); if (temp_string.Length > NumberOfLanguage) Phrases.Add(temp_string[0], temp_string[NumberOfLanguage]); else Phrases.Add(temp_string[0], temp_string[0]); } isStarting = true; } public static bool isStarting // bool for check starting { get; private set; } public static SystemLanguage Language // return current language { get { return language; } } public static string Phrase(string DefaultPhrase, params string[] args) // translate phrase, args use to formating string { #if UNITY_EDITOR if (!isStarting) // if not starting { throw new System.Exception("Forgot initialization.Use Lang.Starting(...)"); // throw exception } #endif string temp_EnglishPhrase = DefaultPhrase; // temp variable for try get value if (Phrases.TryGetValue(DefaultPhrase, out DefaultPhrase)) // if value has been found { if (args.Length == 0) return DefaultPhrase; return string.Format(DefaultPhrase, args); } #if UNITY_EDITOR Add(temp_EnglishPhrase); // add phrase if value hasn't been found #endif if (args.Length == 0) return temp_EnglishPhrase; return string.Format(temp_EnglishPhrase, args); } public static void ChangeLanguage(SystemLanguage _language) // change language { string[] PhrasesArr = Resources.Load<TextAsset>(FileName).text.Split('\n'); // load all text from file NumberOfLanguage = FindIndexLanguage(_language); Phrases.Clear(); // clear dictionary with phrases for (int i = 1; i < PhrasesArr.Length; i++) { string[] temp_string = PhrasesArr[i].Split(';'); if (temp_string.Length > NumberOfLanguage) Phrases.Add(temp_string[0], temp_string[NumberOfLanguage]); else Phrases.Add(temp_string[0], temp_string[0]); } } private static int FindIndexLanguage(SystemLanguage _language) // finding index or current language { int _index = languages.IndexOf(_language); if (_index == -1) // if language not found return 0; // return main language return _index; } #if UNITY_EDITOR private static void Add(string AddString) // add phrases only form editor { File.AppendAllText(Application.dataPath + "/Resources/" + FileName + ".csv", AddString + "\n"); // rewrite text to file Phrases.Add(AddString, AddString); // add phrase to dicrionary AssetDatabase.Refresh(); // refresh file } #endif #if UNITY_EDITOR private static string SetLanguage(SystemLanguage default_language, params SystemLanguage[] _languages) // set first string to file { string ret_string = ""; ret_string += default_language + ";"; foreach (SystemLanguage _language in _languages) { ret_string += _language + ";"; } return ret_string + "!@#$%\n"; // for last index } #endif private static SystemLanguage SystemLanguageParse(string _language) // just parse from string to SystemLanguage { switch (_language) { case "English": return SystemLanguage.English; case "Russian": return SystemLanguage.Russian; case "Ukrainian": return SystemLanguage.Ukrainian; case "Polish": return SystemLanguage.Polish; case "French": return SystemLanguage.French; case "Japanese": return SystemLanguage.Japanese; case "Chinese": return SystemLanguage.Chinese; case "Afrikaans": return SystemLanguage.Afrikaans; case "Arabic": return SystemLanguage.Arabic; case "Basque": return SystemLanguage.Basque; case "Belarusian": return SystemLanguage.Belarusian; case "Bulgarian": return SystemLanguage.Bulgarian; case "ChineseSimplified": return SystemLanguage.ChineseSimplified; case "ChineseTraditional": return SystemLanguage.ChineseTraditional; case "Czech": return SystemLanguage.Czech; case "Danish": return SystemLanguage.Danish; case "Dutch": return SystemLanguage.Dutch; case "Estonian": return SystemLanguage.Estonian; case "Faroese": return SystemLanguage.Faroese; case "Finnish": return SystemLanguage.Finnish; case "German": return SystemLanguage.German; case "Greek": return SystemLanguage.Greek; case "Hebrew": return SystemLanguage.Hebrew; case "Hungarian": return SystemLanguage.Hungarian; case "Icelandic": return SystemLanguage.Icelandic; case "Indonesian": return SystemLanguage.Indonesian; case "Italian": return SystemLanguage.Italian; case "Korean": return SystemLanguage.Korean; case "Latvian": return SystemLanguage.Latvian; case "Lithuanian": return SystemLanguage.Lithuanian; case "Norwegian": return SystemLanguage.Norwegian; case "Portuguese": return SystemLanguage.Portuguese; case "Romanian": return SystemLanguage.Romanian; case "SerboCroatian": return SystemLanguage.SerboCroatian; case "Slovak": return SystemLanguage.Slovak; case "Slovenian": return SystemLanguage.Slovenian; case "Spanish": return SystemLanguage.Spanish; case "Swedish": return SystemLanguage.Swedish; case "Thai": return SystemLanguage.Thai; case "Turkish": return SystemLanguage.Turkish; case "Vietnamese": return SystemLanguage.Vietnamese; } return SystemLanguage.Unknown; } }
Главное запомните: UTF-8-BOM — для работы в Excel, UTF-8 для работы кодом, не забудьте.

