Skip to main content

lib/translations.ex

defmodule WarframeWorldstateDataElixirTools.Translations do
  @moduledoc """
  This module contains methods for translating keys to proper user facing strings.
  """
  @locales [
    :en,
    :cs,
    :de,
    :es,
    :fr,
    :it,
    :ko,
    :pl,
    :pt,
    :ru,
    :sr,
    :tr,
    :uk,
    :zh
  ]

  @data_files %{
    arcanes: "arcanes.json",
    archon_shards: "archonShards.json",
    conclave_data: "conclaveData.json",
    events_data: "eventsData.json",
    factions_data: "factionsData.json",
    fissure_modifiers: "fissureModifiers.json",
    languages: "languages.json",
    mission_types: "missionTypes.json",
    operation_types: "operationTypes.json",
    persistent_enemy_data: "persistentEnemyData.json",
    sol_nodes: "solNodes.json",
    sortie_data: "sortieData.json",
    steel_path: "steelPath.json",
    syndicates_data: "syndicatesData.json",
    synth_targets: "synthTargets.json",
    tutorials: "tutorials.json",
    upgrade_types: "upgradeTypes.json"
  }

  @focus_map %{
    "Focus/Attack" => "Madurai",
    "Focus/Defense" => "Vazarin",
    "Focus/Tactic" => "Naramon",
    "Focus/Power" => "Zenurik",
    "Focus/Ward" => "Unairu"
  }

  @polarity_map %{
    "AP_ATTACK" => "Madurai",
    "AP_DEFENSE" => "Vazarin",
    "AP_TACTIC" => "Naramon",
    "AP_POWER" => "Zenurik",
    "AP_WARD" => "Unairu",
    "AP_UMBRA" => "Umbra",
    "AP_ANY" => "Aura"
  }

  @calendar_event_map %{
    "CET_CHALLENGE" => "To Do",
    "CET_UPGRADE" => "Override",
    "CET_REWARD" => "Big Prize!",
    "CET_PLOT" => "Birthday"
  }

  @archimedia_map %{
    "CT_LAB" => "Deep Archimedea",
    "CT_HEX" => "Temporal Archimedea"
  }

  @doc """
  Gets the translation of the name of an in game node in the requested locale.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.node("SolNode1")
      "Galatea (Neptune)"

      iex> WarframeWorldstateDataElixirTools.Translations.node("SolNode1", :de)
      "Galatea (Neptun)"

      iex> WarframeWorldstateDataElixirTools.Translations.node("SolarSystem/Foo")
      "Foo"

  """
  def node(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) and locale in @locales do
    case sol_node(key, "value", locale) do
      {:ok, value} -> value
      {:error, _} -> key
    end
  end

  @doc """
  Gets the translation of the enemy faction of an in game node in the requested locale.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.node_enemy("SolNode1")
      "Corpus"

      iex> WarframeWorldstateDataElixirTools.Translations.node_enemy("SolNode1", :ko)
      "코퍼스"

  """
  def node_enemy(key, locale \\ :en) do
    case sol_node(key, "enemy", locale) do
      {:ok, value} -> value
      {:error, _} -> key
    end
  end

  @doc """
  Gets the translation of the mission type of an in game node in the requested locale.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.node_mission_type("SolNode1")
      "Capture"

      iex> WarframeWorldstateDataElixirTools.Translations.node_mission_type("SolNode1", :de)
      "Gefangennahme"

  """
  def node_mission_type(key, locale \\ :en) do
    case sol_node(key, "type", locale) do
      {:ok, value} -> value
      {:error, _} -> key
    end
  end

  @doc """
  Gets the translation for a mission type from a missions_type.json file.
  If the translation for the key is not found it returns a "normalized" version of the key.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.mission_type("MT_DEFENSE")
      "Defense"

      iex> WarframeWorldstateDataElixirTools.Translations.mission_type("MT_DEFENSE", :de)
      "Verteidigung"

      iex> WarframeWorldstateDataElixirTools.Translations.mission_type("MT_random")
      "Random"
      
  """
  def mission_type(key, locale \\ :en) when is_binary(key) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.mission_types,
           locale
         ) do
      {:ok, %{^key => %{"value" => value}}} ->
        value

      _ ->
        String.replace(key, "MT_", "")
        |> string_to_title()
    end
  end

  @doc """
  Gets the translation for a fissure's type in the requested locale.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.fissure_type("VoidT1")
      "Lith"

      iex> WarframeWorldstateDataElixirTools.Translations.fissure_type("VoidT1", :de)
      "Lith"
      
      iex> WarframeWorldstateDataElixirTools.Translations.fissure_type("random")
      "random"
  """
  def fissure_type(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) do
    case fissure_modifier(key, "value", locale) do
      {_, res} -> res
    end
  end

  @doc """
  Gets the tier of a fissure by its key in the requested locale.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.fissure_tier("VoidT1")
      1

      iex> WarframeWorldstateDataElixirTools.Translations.fissure_tier("random")
      0
  """
  def fissure_tier(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) do
    case fissure_modifier(key, "num", locale) do
      {:ok, tier} -> tier
      _ -> 0
    end
  end

  @doc """
  Gets the localized string value for a given languages.json key.

  ## Examples

      iex> key = "/lotus/types/challenges/seasons/daily/seasondailycollecthundredresources"
      iex> WarframeWorldstateDataElixirTools.Translations.language_string(key)
      "Gatherer"

      iex> key = "/lotus/types/challenges/seasons/daily/seasondailycollecthundredresources"
      iex> WarframeWorldstateDataElixirTools.Translations.language_string(key, :de)
      "Sammler"

      iex> WarframeWorldstateDataElixirTools.Translations.language_string("")
      "[PH] value"

      iex> WarframeWorldstateDataElixirTools.Translations.language_string("SomeUnknownKey")
      "Some Unknown Key"

  """
  def language_string(key, locale \\ :en)

  def language_string("", _) do
    "[PH] value"
  end

  def language_string(key, locale)
      when is_binary(key) and is_atom(locale) do
    case search_languages(key, "value", locale) do
      {:error, _} ->
        key
        |> last_resource_name()
        |> split_resource_name()
        |> string_to_title()

      value ->
        value
    end
  end

  @doc """
  Gets the localized description for a given languages.json key.

  ## Examples

      iex> key = "/lotus/types/challenges/seasons/daily/seasondailycollecthundredresources"
      iex> WarframeWorldstateDataElixirTools.Translations.language_desc(key)
      "Collect 1,500 Resources"

      iex> key = "/lotus/types/challenges/seasons/daily/seasondailycollecthundredresources"
      iex> WarframeWorldstateDataElixirTools.Translations.language_desc(key, :de)
      "Sammle 1.500 Ressourcen"

      iex> WarframeWorldstateDataElixirTools.Translations.language_desc("")
      "[PH] Description"

      iex> WarframeWorldstateDataElixirTools.Translations.language_desc("SomeUnknownKey")
      "[PH] Some Unknown Key Description"

  """
  def language_desc(key, locale \\ :en)

  def language_desc("", _) do
    "[PH] Description"
  end

  def language_desc(key, locale) when is_binary(key) and is_atom(locale) do
    case search_languages(key, "desc", locale) do
      {:error, _} ->
        res =
          key
          |> last_resource_name()
          |> split_resource_name()
          |> string_to_title()

        "[PH] #{res} Description"

      desc ->
        desc
    end
  end

  @doc """
  Translates a faction key to its display name in the given locale.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.faction("FC_CORPUS")
      "Corpus"

      iex> WarframeWorldstateDataElixirTools.Translations.faction("FC_INFESTATION", :de)
      "Befallene"

      iex> WarframeWorldstateDataElixirTools.Translations.faction("FC_UNKNOWN")
      "FC_UNKNOWN"

  """
  def faction(key, locale \\ :en) when is_binary(key) and is_atom(locale) do
    case search_factions(key, locale) do
      {:ok, faction} ->
        faction

      _ ->
        key
    end
  end

  @doc """
  Returns the display name of a sortie boss for the given key and locale.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_boss("SORTIE_BOSS_HYENA")
      "Hyena Pack"

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_boss("SORTIE_BOSS_UNKNOWN")
      "SORTIE_BOSS_UNKNOWN"

  """
  def sortie_boss(key, locale \\ :en) when is_binary(key) and is_atom(locale) do
    case find_sortie("bosses", key, locale) do
      {:ok, %{"name" => name}} -> name
      _ -> key
    end
  end

  @doc """
  Returns the faction of a sortie boss for the given key and locale.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_faction("SORTIE_BOSS_HYENA")
      "Corpus"

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_faction("SORTIE_BOSS_UNKNOWN")
      "SORTIE_BOSS_UNKNOWN"

  """
  def sortie_faction(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) do
    case find_sortie("bosses", key, locale) do
      {:ok, %{"faction" => faction}} -> faction
      _ -> key
    end
  end

  @doc """
  Returns the display name of a sortie modifier type for the given key and locale.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_modifier("SORTIE_MODIFIER_LOW_ENERGY")
      "Energy Reduction"

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_modifier("SORTIE_MODIFIER_LOW_ENERGY", :de)
      "reduzierte Energie"

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_modifier("SORTIE_MODIFIER_UNKNOWN")
      "SORTIE_MODIFIER_UNKNOWN"

  """
  def sortie_modifier(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) do
    case find_sortie("modifierTypes", key, locale) do
      {:ok, modifier} -> modifier
      _ -> key
    end
  end

  @doc """
  Returns the description of a sortie modifier for the given key and locale.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_modifier_description("SORTIE_MODIFIER_LOW_ENERGY")
      "Maximum Warframe Energy capacity is quartered. Energy Siphon is less effective."

      iex> WarframeWorldstateDataElixirTools.Translations.sortie_modifier_description("SORTIE_MODIFIER_UNKNOWN")
      "SORTIE_MODIFIER_UNKNOWN"

  """
  def sortie_modifier_description(key, locale \\ :en)
      when is_binary(key) and is_atom(locale) do
    case find_sortie("modifierDescriptions", key, locale) do
      {:ok, description} -> description
      _ -> key
    end
  end

  @doc """
  Returns the display name of an Archon Shard for the given color key and locale.

  Falls back to the color key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.archon_shard_color("ACC_BLUE")
      "Azure"

      iex> WarframeWorldstateDataElixirTools.Translations.archon_shard_color("ACC_UNKNOWN")
      "ACC_UNKNOWN"

  """
  def archon_shard_color(color, locale \\ :en)
      when is_binary(color) and is_atom(locale) do
    case archon_shard(color, locale) do
      {:ok, %{"value" => shard_color}} ->
        shard_color

      _ ->
        color
    end
  end

  @doc """
  Returns the translated effect of an Archon Shard upgrade type.

  Falls back to the last path segment of the upgrade_type key if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.archon_shard_upgrade_type("ACC_BLUE", "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax")
      "+150% Health"

      iex> WarframeWorldstateDataElixirTools.Translations.archon_shard_upgrade_type("ACC_BLUE", "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeUnknown")
      "ArchonCrystalUpgradeUnknown"

      iex> WarframeWorldstateDataElixirTools.Translations.archon_shard_upgrade_type("ACC_UNKNOWN", "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax")
      "ArchonCrystalUpgradeWarframeHealthMax"

  """
  def archon_shard_upgrade_type(color, upgrade_type, locale \\ :en)
      when is_binary(color) and is_binary(upgrade_type) and is_atom(locale) do
    case archon_shard(color, locale) do
      {:ok,
       %{"upgradeTypes" => %{^upgrade_type => %{"value" => shard_upgrade_type}}}} ->
        shard_upgrade_type

      _ ->
        last_resource_name(upgrade_type)
    end
  end

  @doc """
  Translates a focus school key to its display name.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.translate_focus("Focus/Attack")
      "Madurai"

      iex> WarframeWorldstateDataElixirTools.Translations.translate_focus("Unknown")
      "None"

  """
  def translate_focus(focus)
      when is_binary(focus) do
    map_value(focus, @focus_map)
  end

  @doc """
  Translates a polarity code to its display name.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.translate_polarity("AP_ATTACK")
      "Madurai"

      iex> WarframeWorldstateDataElixirTools.Translations.translate_polarity("Unknown")
      "None"

  """
  def translate_polarity(polarity)
      when is_binary(polarity) do
    map_value(polarity, @polarity_map)
  end

  @doc """
  Translates a calendar event code to its display name.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.translate_calendar_event("CET_CHALLENGE")
      "To Do"

      iex> WarframeWorldstateDataElixirTools.Translations.translate_calendar_event("Unknown")
      "None"

  """
  def translate_calendar_event(event) when is_binary(event) do
    map_value(event, @calendar_event_map)
  end

  @doc """
  Strips the "CST_" prefix from a season string.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.translate_season("CST_SPRING")
      "SPRING"

      iex> WarframeWorldstateDataElixirTools.Translations.translate_season("SUMMER")
      "SUMMER"

  """
  def translate_season(season) when is_binary(season) do
    String.replace(season, "CST_", "")
  end

  @doc """
  Translates an Archimedea type code to its display name.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.translate_archemedia_type("CT_LAB")
      "Deep Archimedea"

      iex> WarframeWorldstateDataElixirTools.Translations.translate_archemedia_type("Unknown")
      "None"

  """
  def translate_archemedia_type(type) when is_binary(type) do
    map_value(type, @archimedia_map)
  end

  @doc """
  Returns a map containing translated steel path rewards.

  ## Examples

      iex> sp = WarframeWorldstateDataElixirTools.Translations.steel_path()
      iex> is_map(sp)
      true
  """
  def steel_path(locale \\ :en) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.steel_path,
           locale
         ) do
      {:ok, sp} ->
        sp

      _ ->
        %{}
    end
  end

  @doc """
  Returns the translated name of a syndicate.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.syndicate("ArbitersSyndicate", :en)
      "Arbiters of Hexis"

      iex> WarframeWorldstateDataElixirTools.Translations.syndicate("ArbitersSyndicateUnkown")
      "ArbitersSyndicateUnkown"
  """
  def syndicate(key, locale \\ :en) when is_binary(key) and is_atom(locale) do
    case find_syndicate(key, locale) do
      {:ok, %{"name" => name}} ->
        name

      _ ->
        key
    end
  end

  @doc """
  Returns the translated value of a global upgrade.

  Falls back to the key itself if not found.

  ## Examples

      iex> WarframeWorldstateDataElixirTools.Translations.upgrade("GAMEPLAY_MONEY_PICKUP_AMOUNT")
      "Credit Drop amount"

      iex> WarframeWorldstateDataElixirTools.Translations.upgrade("GAMEPLAY_MONEY_PICKUP_AMOUNT_UNKOWN")
      "GAMEPLAY_MONEY_PICKUP_AMOUNT_UNKOWN"
  """
  def upgrade(key, locale \\ :en) when is_binary(key) and is_atom(locale) do
    case find_upgrade(key, locale) do
      {:ok, %{"value" => value}} ->
        value

      _ ->
        key
    end
  end

  defp find_upgrade(key, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.upgrade_types,
           locale
         ) do
      {:ok, %{^key => upgrade}} ->
        {:ok, upgrade}

      _ ->
        {:error, :not_found}
    end
  end

  defp find_syndicate(key, locale) when is_binary(key) and is_atom(locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.syndicates_data,
           locale
         ) do
      {:ok, %{^key => syndicate}} ->
        {:ok, syndicate}

      _ ->
        {:error, :not_found}
    end
  end

  defp archon_shard(color, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.archon_shards,
           locale
         ) do
      {:ok, %{^color => shard}} ->
        {:ok, shard}

      _ ->
        {:error, :not_found}
    end
  end

  defp search_factions(key, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.factions_data,
           locale
         ) do
      {:ok, %{^key => %{"value" => value}}} ->
        {:ok, value}

      _ ->
        {:error, key}
    end
  end

  defp search_languages(key, field, locale)
       when is_binary(key) and is_binary(field) and is_atom(locale) do
    lower_case_key = String.downcase(key)

    with {:error, :not_found} <- find_language(lower_case_key, field, locale) do
      find_language(key, field, locale)
    end
  end

  defp fissure_modifier(key, field, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.fissure_modifiers,
           locale
         ) do
      {:ok, %{^key => %{^field => field_value}}} ->
        {:ok, field_value}

      _ ->
        {:error, key}
    end
  end

  defp sol_node(node_key, field, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.read_data_file(
           @data_files.sol_nodes,
           locale
         ) do
      {:ok, %{^node_key => node}} ->
        {:ok, node[field]}

      {:ok, _} ->
        {:ok, last_resource_name(node_key)}

      {:error, _} = x ->
        x
    end
  end

  defp last_resource_name(res) when is_binary(res) do
    String.split(res, "/", trim: true)
    |> List.last()
  end

  defp string_to_title(str) when is_binary(str) do
    str
    |> String.split()
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
  end

  defp split_resource_name(str) when is_binary(str) do
    splitter = ~r/([A-Z]+(?=[A-Z][a-z]))|([A-Z]?[a-z]+)/

    splitter
    |> Regex.split(str, include_captures: true, trim: true)
    |> Enum.join(" ")
  end

  defp find_language(key, field, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.find_key(
           @data_files.languages,
           key,
           locale
         ) do
      {:ok, %{^field => value}} -> value
      _ -> {:error, :not_found}
    end
  end

  defp find_sortie(key, field, locale) do
    case WarframeWorldstateDataElixirTools.DataBundle.find_key(
           @data_files.sortie_data,
           key,
           locale
         ) do
      {:ok, %{^field => value}} -> {:ok, value}
      _ -> {:error, :not_found}
    end
  end

  defp map_value(key, map) do
    Map.keys(map)
    |> Enum.find_value("None", fn k -> String.contains?(key, k) && map[k] end)
  end
end