Packages
gettext
0.17.2
1.0.2
1.0.1
retired
1.0.0
0.26.2
0.26.1
0.26.0
retired
0.25.0
0.24.0
0.23.1
0.23.0
0.22.3
0.22.2
0.22.1
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.2
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.2
0.12.1
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.0
Internationalization and localization through gettext
Current section
Files
Jump to
Current section
Files
lib/gettext/fuzzy.ex
defmodule Gettext.Fuzzy do
@moduledoc false
alias Gettext.PO
alias Gettext.PO.Translation
alias Gettext.PO.PluralTranslation
@type translation_key :: {binary | nil, binary | {binary, binary}}
@doc """
Returns a matcher function that takes two translation keys and checks if they
match.
`String.jaro_distance/2` (which calculates the Jaro distance) is used to
measure the distance between the two translations. `threshold` is the minimum
distance that means a match. `{:match, distance}` is returned in case of a
match, `:nomatch` otherwise.
"""
@spec matcher(float) :: (translation_key, translation_key -> {:match, float} | :nomatch)
def matcher(threshold) do
fn old_key, new_key ->
distance = jaro_distance(old_key, new_key)
if distance >= threshold, do: {:match, distance}, else: :nomatch
end
end
@doc """
Finds the Jaro distance between the msgids of two translations.
To mimic the behaviour of the `msgmerge` tool, this function only calculates
the Jaro distance of the msgids of the two translations, even if one (or both)
of them is a plural translation.
As per `msgmerge`, the msgctxt of a translation is completely ignored when
calculating the distance.
"""
@spec jaro_distance(translation_key, translation_key) :: float
def jaro_distance({_context1, key1}, {_context2, key2}) do
jaro_distance_on_key(key1, key2)
end
# Apparently, msgmerge only looks at the msgid when performing fuzzy
# matching. This means that if we have two plural translations with similar
# msgids but very different msgid_plurals, they'll still fuzzy match.
def jaro_distance_on_key(key1, key2) when is_binary(key1) and is_binary(key2),
do: String.jaro_distance(key1, key2)
def jaro_distance_on_key({key1, _}, key2) when is_binary(key2),
do: String.jaro_distance(key1, key2)
def jaro_distance_on_key(key1, {key2, _}) when is_binary(key1),
do: String.jaro_distance(key1, key2)
def jaro_distance_on_key({key1, _}, {key2, _}), do: String.jaro_distance(key1, key2)
@doc """
Merges a translation with the corresponding fuzzy match.
`new` is the newest translation and `existing` is the existing translation
that we use to populate the msgstr of the newest translation.
Note that if `new` is a regular translation, then the result will be a regular
translation; if `new` is a plural translation, then the result will be a
plural translation.
"""
@spec merge(PO.translation(), PO.translation()) :: PO.translation()
def merge(new, existing) do
# Everything comes from "new", except for the msgstr and the comments.
new
|> Map.put(:comments, existing.comments)
|> merge_msgstr(existing)
|> PO.Translations.mark_as_fuzzy()
end
defp merge_msgstr(%Translation{} = new, %Translation{} = existing),
do: %{new | msgstr: existing.msgstr}
defp merge_msgstr(%Translation{} = new, %PluralTranslation{} = existing),
do: %{new | msgstr: existing.msgstr[0]}
defp merge_msgstr(%PluralTranslation{} = new, %Translation{} = existing),
do: %{new | msgstr: Map.new(new.msgstr, fn {i, _} -> {i, existing.msgstr} end)}
defp merge_msgstr(%PluralTranslation{} = new, %PluralTranslation{} = existing),
do: %{new | msgstr: existing.msgstr}
end