173 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			173 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
		
			Executable File
		
	
	
| 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;
 | |
|     }
 | |
|   }
 | |
| }
 |