using EasyTalk.Nodes.Tags;
using EasyTalk.Nodes.Variable;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace EasyTalk.Localization
{
///
/// Provides a collection of translations of text for various languages.
///
[CreateAssetMenu(fileName = "Translation Library", menuName = "EasyTalk/Localization/Translation Library", order = 2)]
[Serializable]
public class TranslationLibrary : ScriptableObject
{
///
/// The original/default language for the translation library.
///
[SerializeField]
public string originalLanguage = "en";
///
/// A list of TranslationSets, each containing translations for a specific language.
///
[SerializeField]
public List translationSets = new List();
///
/// Returns the translation for the specified line of text and ISO-639 language code, if it exists.
///
/// The line to translate.
/// The ISO-639 language code to translate to.
/// The translated line, if found; otherwise null.
public Translation GetTranslation(string line, string targetLanguageCode)
{
if(translationSets.Count == 0) { return null; }
TranslationSet sourceSet = GetOrCreateOriginalTranslationSet();
Translation lineTranslation = sourceSet.FindTranslation(line);
TranslationSet targetSet = FindTranslationSetForLanguage(targetLanguageCode);
if(lineTranslation != null && targetSet != null)
{
Translation translation = targetSet.GetTranslation(lineTranslation.id);
if(translation != null) { return translation; }
}
Debug.LogWarning($"Line '{line}' is missing translation to {targetLanguageCode}");
return lineTranslation;
}
///
/// Retrieves or creates a translation set for the default/original language of the library.
///
/// A TranslationSet for the default/original language of the library.
public TranslationSet GetOrCreateOriginalTranslationSet()
{
TranslationSet sourceSet = FindTranslationSetForLanguage(originalLanguage);
if (sourceSet == null)
{
sourceSet = new TranslationSet();
sourceSet.languageCode = originalLanguage;
translationSets.Insert(0, sourceSet);
}
return sourceSet;
}
///
/// Returns true if the library contains a TranslationSet for the ISO-639 language code provided.
///
/// The ISO-639 language code to check for a translation set.
/// True if a TranslationSet is found for the specified ISO-639 language code.
public bool HasTranslationSet(string languageCode)
{
foreach(TranslationSet translationSet in translationSets)
{
if(translationSet.languageCode.Equals(languageCode))
{
return true;
}
}
return false;
}
///
/// Add a TranslationSet for the ISO-639 language code specified if it doesn't exist in the library.
///
/// The ISO-639 language code to add a TranslationSet for.
/// The TranslationSet for the language code specified.
public TranslationSet AddSecondaryTranslationSet(string languageCode)
{
TranslationSet translationSet = FindTranslationSetForLanguage(languageCode);
if (translationSet == null)
{
translationSet = new TranslationSet();
translationSet.languageCode = languageCode;
translationSets.Add(translationSet);
}
TranslationSet originalTranslationSet = FindTranslationSetForLanguage(originalLanguage);
translationSet.CopyStructure(originalTranslationSet);
return translationSet;
}
///
/// Finds and returns the TranslationSet for the specified ISO-639 language code.
///
/// The ISO-639 language code to find a TranslationSet for.
/// The TranslationSet for the specified language, if it exists; null otherwise.
public TranslationSet FindTranslationSetForLanguage(string languageCode)
{
foreach (TranslationSet translationSet in translationSets)
{
if (translationSet.languageCode.Equals(languageCode))
{
return translationSet;
}
}
return null;
}
///
/// Returns a List of untranslated Translations (lines) for the specified ISO-639 language code. Any empty entries in the found TranslationSet are assumed to be
/// untranslated.
///
/// The ISO-639 language code of the TranslationSet to get untranslated lines for.
/// A List of untranslated lines for the language code specified.
public List GetUntranslatedLines(string languageCode)
{
TranslationSet sourceSet = FindTranslationSetForLanguage(originalLanguage);
TranslationSet targetSet = FindTranslationSetForLanguage(languageCode);
List untranslatedLines = new List();
foreach(Translation sourceTranslation in sourceSet.translations)
{
Translation targetTranslation = targetSet.GetTranslation(sourceTranslation.id);
if (targetTranslation == null || targetTranslation.text == null || targetTranslation.text.Length == 0)
{
untranslatedLines.Add(sourceTranslation);
}
}
return untranslatedLines;
}
///
/// Changes the original language of the library to the specified ISO-639 language code and adds a TranslationSet for it if one doesn't exist for that language.
///
/// The ISO-639 language code to change to.
public void SetOriginalLanguage(string languageCode)
{
if(!this.originalLanguage.Equals(languageCode))
{
bool foundSet = false;
for(int i = 0; i < translationSets.Count; i++)
{
TranslationSet translationSet = translationSets[i];
if(translationSet.languageCode.Equals(languageCode))
{
translationSets.RemoveAt(i);
translationSets.Insert(0, translationSet);
foundSet = true;
break;
}
}
if(!foundSet)
{
TranslationSet newSourceSet = new TranslationSet();
newSourceSet.languageCode = languageCode;
this.translationSets.Insert(0, newSourceSet);
}
this.originalLanguage = languageCode;
}
}
}
///
/// A TranslationSet contains lines of text which have been translated into a particular language.
///
[Serializable]
public class TranslationSet
{
///
/// The current counter of the translation set.
///
[HideInInspector]
[SerializeField]
private int counter = 0;
///
/// The ISO-639 language code for the translation set.
///
[SerializeField]
public string languageCode;
///
/// A List of translated liens for the translation set.
///
[SerializeField]
public List translations = new List();
///
/// Whether node tags and TextMeshPro/HTML markup tags should be removed from text prior to putting it into the list of translated lines.
///
private bool preventTagInclusion = true;
///
/// Checks the translation set to see if the text provided is in the current list of translations. If it isn't, a new translation entry is added for the text.
///
/// The text to find/add a translation for.
/// The Translation for the translated line.
public Translation AddOrFindTranslation(string text)
{
string finalText = text;
if (preventTagInclusion)
{
finalText = RemoveAllTags(text);
}
Translation translation = FindTranslation(finalText);
if (translation == null)
{
translation = new Translation(counter, languageCode, finalText);
this.translations.Add(translation);
counter++;
}
return translation;
}
///
/// Attempts to add a new translation for the specified translation ID.
///
/// The source ID the translation applies to.
/// The translated text.
/// The added Translation, or null if a translation couldn't be added.
private Translation TryAddTranslation(int id, string text)
{
Translation translation = GetTranslation(id);
if (translation == null)
{
string finalText = text;
if (preventTagInclusion)
{
finalText = RemoveAllTags(text);
}
translation = new Translation(id, languageCode, finalText);
this.translations.Add(translation);
if (id > counter)
{
counter = id + 1;
}
return translation;
}
else
{
Debug.LogWarning("A translation already exists for ID:" + id + " Text:'" + text + "', Language = '" + languageCode + "'. " +
"Translation for '" + text + "' will not be created.");
return null;
}
}
///
/// Copies the structure of the provided TranslationSet, adding an entry with an identical ID for each translation.
///
/// The translation set to copy the structure of.
public void CopyStructure(TranslationSet set)
{
for(int i = 0; i < set.translations.Count; i++)
{
Translation translation = set.translations[i];
bool found = false;
for(int j = 0; j < translations.Count; j++)
{
if (translations[j].id == translation.id)
{
found = true;
break;
}
}
if(!found)
{
translations.Add(new Translation(translation.id, languageCode, ""));
counter = Math.Max(counter + 1, translation.id + 1);
}
}
}
///
/// Sets the translation at the specified source ID to the value provided.
///
/// The source ID the translation applies to.
/// The translated text to set.
public void SetTranslation(int id, string text)
{
bool translationFound = false;
for(int i = 0; i < translations.Count; i++)
{
Translation translation = translations[i];
if(translation.id == id)
{
string finalText = text;
if (preventTagInclusion)
{
finalText = RemoveAllTags(text);
}
translation.text = finalText;
translationFound = true;
break;
}
}
if(!translationFound)
{
TryAddTranslation(id, text);
}
}
///
/// Returns the Translation for the specified ID, if it exists.
///
/// The ID of the Translation to retrieve.
/// The Translation for the specified ID if it exists; null otherwise.
public Translation GetTranslation(int id)
{
for (int i = 0; i < translations.Count; i++)
{
Translation translation = translations[i];
if (translation.id == id)
{
return translation;
}
}
return null;
}
///
/// Returns whether the translation set contains a translation with the text specified.
///
/// The translation text to check.
/// True if the translation set contains the text provided; otherwise returns false.
public bool ContainsTranslation(string text)
{
string finalText = text;
if (preventTagInclusion)
{
finalText = RemoveAllTags(text);
}
foreach (Translation translation in translations)
{
if (translation.text.Equals(finalText))
{
return true;
}
}
return false;
}
///
/// Finds the Translation object attributed to the text specified, if it exists.
///
/// The text to find a Translation object for.
/// The Translation object which contains the text specified, if it exists; otherwise returns null.
public Translation FindTranslation(string text)
{
string finalText = text;
if (preventTagInclusion)
{
finalText = RemoveAllTags(text);
}
for (int i = 0; i < translations.Count; i++)
{
if (translations[i].text.Equals(finalText))
{
return translations[i];
}
}
return null;
}
///
/// Removes all node tags and TextMeshPro/HTML markup tags from the string provided.
///
/// The string to remove tags from.
/// A modified version of the original string will all tags removed.
public static string RemoveAllTags(string text)
{
string newText = RemoveTMPTags(text);
newText = RemoveNodeTags(newText);
return newText;
}
///
/// Removes TextMeshPro/HTML markup tags from the string provided.
///
/// The string to remove tags from.
/// A modified version of the original string with all TextMeshPro/HTML markup tags removed.
public static string RemoveTMPTags(string text)
{
return TMPTag.RemoveTags(text);
}
///
/// Removes all node tags from the string provided.
///
/// The string to remove tags from.
/// A modified version of the original string with all node tags removed.
public static string RemoveNodeTags(string text)
{
return NodeTag.RemoveTags(text);
}
///
/// Replaces all variable references in the provided string with numeric, indexed variable names. The new variable names are stored as keys in the variableNameMap provided and
/// the original variable names are stored as the respective values.
///
/// The text to index variable references on.
/// A Dictionary to insert mappings of indexed variable names to old variable names into.
/// A modified version of the original string, with all variable names replaced by indexed variable names.
public static string IndexVariableNames(string text, Dictionary variableNameMap)
{
return NodeVariable.IndexVariablesNames(text, variableNameMap);
}
///
/// Replaces all variable references in the provided string with the respective variable names stored as values in the provided Dictionary. Each of the variable
/// names referenced in the string must match a key in the variableNameMap in order to be replaced.
///
/// The string to replace variable names in.
/// A Dictionary with current variable names as keys and replacement variable names as values.
/// A modified version of the original string, with all variable names replaced if they were found as keys in the Dictionary.
public static string ReplaceVariables(string text, Dictionary variableNameMap)
{
return NodeVariable.ReplaceVariablesNames(text, variableNameMap);
}
}
///
/// A Translation contains a string which is a translation of text into a specific language, as well as other secondary information about that translation.
///
[Serializable]
public class Translation : IComparable
{
///
/// The ID of the translation. This ID should be the same as the ID for the equivalent translation of other languages in the same library.
///
[SerializeField]
public int id;
///
/// The ISO-639 language code of the translation.
///
[SerializeField]
public string language;
///
/// The text of the translation.
///
[SerializeField]
public string text;
///
/// Creates a new Translation.
///
public Translation() { }
///
/// Creates a new Translation with the specified ID and ISO-639 language code.
///
/// The ID of the Translation.
/// The ISO-639 language code to use.
public Translation(int id, string languageCode) : this(languageCode)
{
this.id = id;
}
///
/// Creates a new Translation with the specified ID, language code, and text.
///
/// The ID of the Translation.
/// The ISO-639 language code to use.
/// The text of the translation.
public Translation(int id, string languageCode, string text) : this(id, languageCode)
{
this.text = text;
}
///
/// Creates a new Translation with the specified ISO-639 language code.
///
/// The ISO-639 language code to use.
public Translation(string languageCode) : this()
{
this.language = languageCode;
}
///
/// Compares the Translation to another Translation.
///
/// The other Translation to compare to.
/// The comparison value.
public int CompareTo(Translation other)
{
if (this.id < other.id)
{
return -1;
}
return this.text.CompareTo(other.text);
}
}
}