173 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
		
		
			
		
	
	
			173 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
|  | using System; | ||
|  | using System.Text.RegularExpressions; | ||
|  | using System.Collections.Generic; | ||
|  | using System.IO; | ||
|  | using UnityEngine; | ||
|  | 
 | ||
|  | namespace GoogleMobileAds.Editor | ||
|  | { | ||
|  |   public class EditorLocalization | ||
|  |   { | ||
|  |     private const string LOCALIZATION_DATA_JSON_RELATIVE_PATH = "GoogleMobileAds/Editor"; | ||
|  |     private const string LOCALIZATION_DATA_JSON_FILENAME = | ||
|  |       "gma_settings_editor_localization_data.json"; | ||
|  |     private const string LOCALIZATIONS_JSON_KEY = "LocalizationsByKey"; | ||
|  |     private const string LOCALIZATION_KEY_PREFIX = "KEY_"; | ||
|  | 
 | ||
|  |     private readonly Lazy<EditorLocalizationData> _localizationData = | ||
|  |       new Lazy<EditorLocalizationData>(() => InitLocalizationDataOrThrow()); | ||
|  |     private EditorLocalizationData GetLocalizationData() => _localizationData.Value; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Gets the default language for the settings editor. | ||
|  |      * We assume the default locale used belong to the list of supported cultures | ||
|  |      * (https://www.csharp-examples.net/culture-names/), and that each key has a default | ||
|  |      * localization provided. | ||
|  |      */ | ||
|  |     public string GetDefaultLanguage() | ||
|  |     { | ||
|  |       return "en"; // English | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Checks that a localization key exists. | ||
|  |      */ | ||
|  |     public bool HasKey(string key) | ||
|  |     { | ||
|  |       return GetLocalizationData().LocalizationsByKey.ContainsKey(key); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Localizes a resource key based on a provided user language. | ||
|  |      * Returns the key name if the key could not be localized. | ||
|  |      */ | ||
|  |     public string ForKey(string key) | ||
|  |     { | ||
|  |       key = key.ToUpper(); | ||
|  |       // Accept both key syntaxes. | ||
|  |       if (key.StartsWith(LOCALIZATION_KEY_PREFIX)) | ||
|  |           key = key.Replace(LOCALIZATION_KEY_PREFIX, ""); | ||
|  | 
 | ||
|  |       if (GetLocalizationData().LocalizationsByKey.TryGetValue(key, | ||
|  |           out Dictionary<string, string> localizations)) | ||
|  |       { | ||
|  |           // Key was found. Try to localize the key with the user language (e.g., "en" or "fr"). | ||
|  |           // Else, use the default (fallback) language, if the localization key is missing for | ||
|  |           // the chosen language (or no language was selected). | ||
|  |           // The region is omitted purposely as we don't currently require this level of details. | ||
|  |           string userLanguage = GoogleMobileAdsSettings.LoadInstance().UserLanguage; | ||
|  |           if (localizations == null) | ||
|  |           { | ||
|  |             return null; | ||
|  |           } | ||
|  |           bool userLanguageExists = localizations.TryGetValue(userLanguage, | ||
|  |                                                               out string userLocalization); | ||
|  |           bool userLocalizationIsValid = userLanguageExists && | ||
|  |               !string.IsNullOrEmpty(userLocalization); | ||
|  |           return userLocalizationIsValid ? userLocalization: localizations[GetDefaultLanguage()]; | ||
|  |       } | ||
|  | 
 | ||
|  |       // Error, key not found, no localization to return so let's fallback to the key name | ||
|  |       // to provide some sort of indication in the UI. | ||
|  |       Debug.LogError($"Localization key not found: {key}."); | ||
|  |       return key; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Deserializes the localization data, encoded in json. | ||
|  |      * Returns the json deserialized to a EditorLocalizationData class instance. | ||
|  |      * Throws an ArgumentException if the json file cannot be deserialized. | ||
|  |      */ | ||
|  |     private static EditorLocalizationData InitLocalizationDataOrThrow() | ||
|  |     { | ||
|  |       string localizationDataPath = | ||
|  |         Path.Combine(Application.dataPath, LOCALIZATION_DATA_JSON_RELATIVE_PATH, | ||
|  |           LOCALIZATION_DATA_JSON_FILENAME); | ||
|  |       // Handle importing the localization data file via Unity Package Manager. | ||
|  |       var pathUtils = ScriptableObject.CreateInstance<EditorPathUtils>(); | ||
|  |       if (pathUtils.IsPackageRootPath()) | ||
|  |       { | ||
|  |         localizationDataPath = | ||
|  |             Path.Combine(pathUtils.GetDirectoryAssetPath(), LOCALIZATION_DATA_JSON_FILENAME); | ||
|  |       } | ||
|  |       try | ||
|  |       { | ||
|  |         string json = File.ReadAllText(localizationDataPath); | ||
|  |         var data = DeserializeFromJson(json); | ||
|  |         if (data.LocalizationsByKey == null) | ||
|  |         { | ||
|  |           throw new ArgumentNullException("LocalizationsByKey"); | ||
|  |         } | ||
|  |         return data; | ||
|  |       } | ||
|  |       catch (Exception) | ||
|  |       { | ||
|  |         throw new ArgumentException( | ||
|  |           $"Exception thrown while retrieving localization data from {localizationDataPath}:" + | ||
|  |           " {ex:full}"); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // We would like to handle the deserialization of the JSON file referenced above but without | ||
|  |     // leveraging any JSON library to avoid adding any dependency. | ||
|  |     private static EditorLocalizationData DeserializeFromJson(string json) | ||
|  |     { | ||
|  |       var data = new EditorLocalizationData(); | ||
|  |       data.LocalizationsByKey = new Dictionary<string, Dictionary<string, string>>(); | ||
|  |       // We match every field in the JSON. The order in which those matches are found is used to | ||
|  |       // deserialize the localization values. | ||
|  |       var regex = new Regex(@"""(?<val>[^""]+)"""); | ||
|  |       var matches = regex.Matches(json); | ||
|  |       var currentKeys = new List<string>(); | ||
|  |       var valueProcessed = false; | ||
|  |       foreach (Match match in matches) | ||
|  |       { | ||
|  |         var val = match.Groups["val"].Value; | ||
|  |         if (val.Equals(LOCALIZATIONS_JSON_KEY)) | ||
|  |         { | ||
|  |           currentKeys.Clear(); | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (valueProcessed) | ||
|  |         { | ||
|  |           valueProcessed = false; | ||
|  |           if (val.StartsWith(LOCALIZATION_KEY_PREFIX)) | ||
|  |           { | ||
|  |             // Start a new level. | ||
|  |             currentKeys.Clear(); | ||
|  |           } | ||
|  |           else if (currentKeys.Count > 0) | ||
|  |           { | ||
|  |             // Go up one level by removing the latest key. | ||
|  |             currentKeys.RemoveAt(currentKeys.Count - 1); | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         // The localization values are 2 levels deep. | ||
|  |         if (currentKeys.Count < 2) | ||
|  |         { | ||
|  |           currentKeys.Add(val); | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         ProcessValue(data, currentKeys, val); | ||
|  |         valueProcessed = true; | ||
|  |       } | ||
|  | 
 | ||
|  |       return data; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static void ProcessValue(EditorLocalizationData data, List<string> currentKeys, | ||
|  |                                      string val) | ||
|  |     { | ||
|  |       if (currentKeys.Count != 2) | ||
|  |         return; | ||
|  |       currentKeys[0] = currentKeys[0].Replace(LOCALIZATION_KEY_PREFIX, ""); | ||
|  |       if (!data.LocalizationsByKey.ContainsKey(currentKeys[0])) | ||
|  |         data.LocalizationsByKey[currentKeys[0]] = new Dictionary<string, string>(); | ||
|  |       data.LocalizationsByKey[currentKeys[0]][currentKeys[1]] = val; | ||
|  |     } | ||
|  |   } | ||
|  | } |