Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle multi language input fields in web ui #141

Merged
merged 31 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a54d8fe
Handle multi language input fields in web ui
eighttrigrams Mar 20, 2023
4cec4df
Wrap I18N.getTranslation
eighttrigrams Mar 20, 2023
75b94c4
Add note
eighttrigrams Mar 20, 2023
c2a72a4
Add todo
eighttrigrams Mar 20, 2023
36cb948
Lay out i18n field conversion
eighttrigrams Mar 20, 2023
b2f0f1d
Simplify test
eighttrigrams Mar 20, 2023
4191775
Simplify test
eighttrigrams Mar 20, 2023
aad5f6e
Extract module
eighttrigrams Mar 20, 2023
2e3b656
Don't touch field if already a map
eighttrigrams Mar 20, 2023
ca9d9a3
Handle simpleInput
eighttrigrams Mar 20, 2023
5f56fe7
Handle multi input
eighttrigrams Mar 20, 2023
6e14652
Write more compact
eighttrigrams Mar 20, 2023
01c4d92
Improve test readability
eighttrigrams Mar 20, 2023
60149e1
Merge branch 'master' into handle-multi-language-input-fields-in-ui
eighttrigrams Mar 26, 2023
1c6e208
Display multi language field for dimension, in web ui
eighttrigrams Mar 26, 2023
a8511d8
Display multi language field for dating, in web ui
eighttrigrams Mar 26, 2023
5307495
Change key order
eighttrigrams Mar 26, 2023
c85a7c9
Convert legacy dating fields to multi language
eighttrigrams Mar 26, 2023
230a7b3
Convert legacy dimension fields to multi language
eighttrigrams Mar 26, 2023
887857b
Remove log msg
eighttrigrams Mar 26, 2023
d10a5f5
Add unspecifiedLanguage to fallback USER_INTERFACE_LANGUAGES
eighttrigrams Mar 26, 2023
b4037a9
Merge remote-tracking branch 'origin/master' into handle-multi-langua…
eighttrigrams Mar 30, 2023
3c6b6b8
Use text where instead multiInput
eighttrigrams Mar 30, 2023
56145a8
Handle multiInputFields
eighttrigrams Mar 30, 2023
bb2a4a4
Add in 'unspecifiedLanguage' in initializeLanguages()
eighttrigrams Mar 30, 2023
2d3cd16
Simplify getTranslation function
eighttrigrams Mar 31, 2023
c7dafb7
Update enricher.ex
eighttrigrams Mar 31, 2023
ac96e11
Remove unnessecary brackets in concat
eighttrigrams Mar 31, 2023
141f6e3
Merge branch 'handle-multi-language-input-fields-in-ui' of https://gi…
eighttrigrams Mar 31, 2023
3ff6617
Translate fields in ProjectHome
eighttrigrams Apr 1, 2023
c603def
Adjust some typings from string to I18N.String
eighttrigrams Apr 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion web/api/lib/worker/enricher/enricher.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
defmodule Api.Worker.Enricher.Enricher do
defmodule Api.Worker.Enricher.Enricher do # TODO pull artifact up one level
alias Api.Worker.Enricher.Preprocess
alias Api.Worker.Enricher.I18NFieldConverter
alias Api.Worker.Enricher.Gazetteer
alias Api.Worker.Enricher.Relations
alias Api.Worker.Enricher.Labels
alias Api.Worker.Enricher.Children

require Logger

def process(changes, project, get_for_id, configuration) do
Expand All @@ -17,6 +19,7 @@ defmodule Api.Worker.Enricher.Enricher do

defp process_change(change = %{ deleted: true }, _project, _get_for_id, _configuration), do: change
defp process_change(change, project, get_for_id, configuration) do

try do
change
|> Preprocess.add_sort_field
Expand All @@ -25,6 +28,7 @@ defmodule Api.Worker.Enricher.Enricher do
|> Relations.expand(get_for_id)
|> Labels.add_labels(configuration)
|> put_in([:doc, :project], project)
|> I18NFieldConverter.convert(configuration)
rescue
error ->
Logger.error "Enrichment failed for resource #{change.doc.resource.id}: "
Expand Down
95 changes: 95 additions & 0 deletions web/api/lib/worker/enricher/i18n_field_converter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
defmodule Api.Worker.Enricher.I18NFieldConverter do

alias Api.Core.CategoryTreeList
alias Api.Worker.Enricher.Utils

defp convert_string(resource, field_name, field_value) when not is_map(field_value) do # this is from legacy project then
put_in(resource, [field_name], %{ "unspecifiedLanguage" => field_value })
end
defp convert_string resource, _field_name, _field_value do
resource
end

defp convert_string_array_item(item) when not is_map(item) do # legacy
%{ "unspecifiedLanguage" => item }
end
defp convert_string_array_item item do
item
end

defp convert_string_array(resource, field_name, field_value) do
put_in(resource, [field_name], Enum.map(field_value, &convert_string_array_item/1))
end

defp convert_dating_source(dating_item_source) when not is_map(dating_item_source) do # from legacy project
# TODO review, here we use keyword, above we use string
%{ unspecifiedLanguage: dating_item_source }
end
defp convert_dating_source dating_item_source do
dating_item_source
end

defp convert_dating_item dating_item do
put_in(dating_item.source, convert_dating_source(dating_item.source))
end

defp convert_dating resource, field_name, field_value do
put_in(resource, [field_name], Enum.map(field_value, &convert_dating_item/1))
end

defp convert_dimension_measurement_comment(dimension_measurement_comment) when not is_map(dimension_measurement_comment) do # legacy project
%{ unspecifiedLanguage: dimension_measurement_comment }
end
defp convert_dimension_measurement_comment dimension_measurement_comment do
dimension_measurement_comment
end

defp convert_dimension_item dimension_item do
put_in(dimension_item.measurementComment, convert_dimension_measurement_comment(dimension_item.measurementComment))
end

defp convert_dimension resource, field_name, field_value do
put_in(resource, [field_name], Enum.map(field_value, &convert_dimension_item/1))
end

defp convert_resource_field category_definition_groups do
fn {field_name, field_value}, resource ->
field_definition = Utils.get_field_definition category_definition_groups, Atom.to_string(field_name)

if is_nil(field_definition[:inputType]) do
resource
else
cond do
field_definition.inputType == "dating" ->
convert_dating resource, field_name, field_value

field_definition.inputType == "dimension" ->
convert_dimension resource, field_name, field_value

field_definition.inputType == "multiInput"
or field_definition.inputType == "simpleMultiInput" ->
convert_string_array resource, field_name, field_value

field_definition.inputType == "input"
or field_definition.inputType == "simpleInput"
or field_definition.inputType == "text" ->
convert_string resource, field_name, field_value

true ->
resource
end
end
end
end

def convert_category change = %{ doc: %{ resource: resource } }, category_definition_groups do
resource = Enum.reduce(resource, resource, convert_resource_field(category_definition_groups))
put_in(change.doc.resource, resource)
end

def convert change, configuration do
name = change.doc.resource.category.name # TODO review category.name, maybe document the expectation
category_definition = CategoryTreeList.find_by_name(name, configuration)
convert_category change, category_definition.groups
end
end
19 changes: 5 additions & 14 deletions web/api/lib/worker/enricher/labels.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
defmodule Api.Worker.Enricher.Labels do
require Logger
alias Api.Core.Utils
alias Api.Core
alias Api.Core.CategoryTreeList
alias Api.Worker.Enricher.Utils

@core_properties [:id, :identifier, :shortDescription, :geometry, :geometry_wgs84, :georeference,
:gazId, :parentId, :featureVectors, :license, :shortName, :originalFilename]
Expand All @@ -18,7 +19,7 @@ defmodule Api.Worker.Enricher.Labels do
else
Enum.reduce(resource, %{}, add_labels_to_field(category_definition, configuration))
|> Enum.into(%{})
|> Utils.atomize
|> Core.Utils.atomize
end
end

Expand Down Expand Up @@ -49,7 +50,7 @@ defmodule Api.Worker.Enricher.Labels do
cond do
Enum.member?(@core_properties, field_name) -> put_in(resource, [field_name], field_value)

get_field_definition(category_definition, field_name) == nil ->
Utils.get_field_definition(category_definition.groups, field_name) == nil ->
Logger.warn "Label not found: field \"#{field_name}\" of category \"#{category_definition.name}\""
resource
true ->
Expand Down Expand Up @@ -94,7 +95,7 @@ defmodule Api.Worker.Enricher.Labels do

defp get_label(_, nil, _), do: nil
defp get_label(field_name, field_value, category_definition) do
field_definition = get_field_definition(category_definition, field_name)
field_definition = Utils.get_field_definition(category_definition.groups, field_name)
cond do
is_nil(field_definition) -> raise "No field definition found for field #{field_name} of category "
<> category_definition.name
Expand All @@ -105,16 +106,6 @@ defmodule Api.Worker.Enricher.Labels do
end
end

defp get_field_definition(category_definition, field_name) do
group = Enum.find(category_definition.groups, &get_field_definition_from_group(&1, field_name))
get_field_definition_from_group(group, field_name)
end

defp get_field_definition_from_group(%{ fields: fields }, field_name) do
Enum.find(fields, fn field -> field.name == field_name end)
end
defp get_field_definition_from_group(_, _), do: nil

defp get_labels_object(%{ "labels" => labels }), do: labels
defp get_labels_object(_), do: %{}
end
13 changes: 13 additions & 0 deletions web/api/lib/worker/enricher/utils.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Api.Worker.Enricher.Utils do

# TODO extract
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Das TODO kann raus, oder?

def get_field_definition(category_definition_groups, field_name) do
group = Enum.find(category_definition_groups, &get_field_definition_from_group(&1, field_name))
get_field_definition_from_group(group, field_name)
end

defp get_field_definition_from_group(%{ fields: fields }, field_name) do
Enum.find(fields, fn field -> field.name == field_name end)
end
defp get_field_definition_from_group(_, _), do: nil
end
116 changes: 116 additions & 0 deletions web/api/test/unit/worker/enricher/i18n_field_converter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
defmodule Api.Worker.Enricher.I18NFieldConverterTest do

use ExUnit.Case, async: true
use Plug.Test
alias Api.Worker.Enricher.I18NFieldConverter

test "convert input, simpleInput, multiInput, simpleMultiInput" do
change = %{
doc: %{
resource: %{
category: %{
name: "Trench"
},
legacyInputField: "hallo",
inputField: %{ "de" => "hallo-de" },
simpleInputField: "hallo-simple-input",
legacyTextField: "hallo\ntext",
textField: %{ "de" => "hallo-de\ntext" },
legacyMultiInputField: ["a", "b"],
multiInputField: [%{ "de" => "a" }, %{ "de" => "b" }],
simpleMultiInputField: ["a", "b"]
}
},
}
category_definition_groups =
[
%{
fields: [
%{ inputType: "input", name: "legacyInputField" },
%{ inputType: "input", name: "inputField" },
%{ inputType: "simpleInput", name: "simpleInputField" },
%{ inputType: "text", name: "legacyTextField" },
%{ inputType: "text", name: "textField" },
%{ inputType: "multiInput", name: "legacyMultiInputField" },
%{ inputType: "multiInput", name: "multiInputField" },
%{ inputType: "simpleMultiInput", name: "simpleMultiInputField" }
]
}
]

resource = (I18NFieldConverter.convert_category change, category_definition_groups).doc.resource

assert %{ "unspecifiedLanguage" => "hallo" } == resource.legacyInputField
assert %{ "de" => "hallo-de" } == resource.inputField
assert %{ "unspecifiedLanguage" => "hallo-simple-input" } == resource.simpleInputField
assert %{ "unspecifiedLanguage" => "hallo\ntext" } == resource.legacyTextField
assert %{ "de" => "hallo-de\ntext" } == resource.textField
assert [%{ "unspecifiedLanguage" => "a" }, %{ "unspecifiedLanguage" => "b" }] == resource.legacyMultiInputField
assert [%{ "de" => "a" }, %{ "de" => "b" }] == resource.multiInputField
assert [%{ "unspecifiedLanguage" => "a" }, %{ "unspecifiedLanguage" => "b" }] == resource.simpleMultiInputField
end

test "convert dating" do
category_definition_groups =
[
%{
fields: [
%{ inputType: "dating", name: "datingField" },
%{ inputType: "dating", name: "legacyDatingField" },
]
}
]
change = %{
doc: %{
resource: %{
category: %{
name: "Trench"
},
datingField: [%{
source: %{de: "Eine Datierung", en: "A Dating"}
}],
legacyDatingField: [%{
source: "Eine Datierung"
}]
}
},
}

resource = (I18NFieldConverter.convert_category change, category_definition_groups).doc.resource

assert %{ de: "Eine Datierung", en: "A Dating" } == (List.first resource.datingField).source
assert %{ unspecifiedLanguage: "Eine Datierung" } == (List.first resource.legacyDatingField).source
end

test "convert dimension" do
category_definition_groups =
[
%{
fields: [
%{ inputType: "dimension", name: "dimensionField" },
%{ inputType: "dimension", name: "legacyDimensionField" },
]
}
]
change = %{
doc: %{
resource: %{
category: %{
name: "Trench"
},
dimensionField: [%{
measurementComment: %{de: "Eine Abmessung", en: "A dimension"}
}],
legacyDimensionField: [%{
measurementComment: "Eine Abmessung"
}]
}
},
}

resource = (I18NFieldConverter.convert_category change, category_definition_groups).doc.resource

assert %{ de: "Eine Abmessung", en: "A dimension" } == (List.first resource.dimensionField).measurementComment
assert %{ unspecifiedLanguage: "Eine Abmessung" } == (List.first resource.legacyDimensionField).measurementComment
end
end
25 changes: 15 additions & 10 deletions web/ui/src/idai_field/project/ProjectHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Card } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import { I18N } from 'idai-field-core';
import { Document, FieldValue, getDocumentImages, getFieldValue } from '../../api/document';
import { get, search } from '../../api/documents';
import { buildProjectQueryTemplate, parseFrontendGetParams, Query } from '../../api/query';
Expand All @@ -25,6 +26,7 @@ import { EXCLUDED_CATEGORIES } from '../constants';
import CategoryFilter from '../filter/CategoryFilter';
import ProjectHierarchyButton from './ProjectHierarchyButton';
import ProjectMap from './ProjectMap';
import { getTranslation } from '../../shared/languages';


const MAP_FIT_OPTIONS = { padding : [ 10, 10, 10, 10 ], duration: 500 };
Expand Down Expand Up @@ -87,17 +89,20 @@ export default function ProjectHome(): ReactElement {
}


const renderTitle = (title: string, projectDocument: Document) =>
<div className="d-flex p-2 m-2" style={ headerStyle }>
<div className="flex-fill">
<h2><img src="/marker-icon.svg" alt="Home" style={ homeIconStyle } /> {title}</h2>
</div>
<div className="text-right" style={ buttonsStyle }>
<LicenseInformationButton license={ projectDocument.resource.license } />
<DocumentPermalinkButton url={ getDocumentPermalink(projectDocument) } />
</div>
</div>;
const renderTitle = (title: string, projectDocument: Document) => {

const titleStr = getTranslation(title as undefined as I18N.String);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix typing. param title should be of type I18N.String.
auch nochmal gucken, wenn ich den rückgabewert vom getTranslation anpasse (siehe unten, '' vs undefined)


return (<div className="d-flex p-2 m-2" style={ headerStyle }>
<div className="flex-fill">
<h2><img src="/marker-icon.svg" alt="Home" style={ homeIconStyle } /> {titleStr}</h2>
</div>
<div className="text-right" style={ buttonsStyle }>
<LicenseInformationButton license={ projectDocument.resource.license } />
<DocumentPermalinkButton url={ getDocumentPermalink(projectDocument) } />
</div>
</div>);
};

const renderSidebar = (projectId: string, projectDocument: Document, categoryFilter: ResultFilter,
setHighlightedCategories: (categories: string[]) => void, t: TFunction, typeCatalogCount: number,
Expand Down
12 changes: 8 additions & 4 deletions web/ui/src/idai_field/projects.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { ResultDocument } from '../api/result';
import { Document } from '../api/document';
import { getTranslation } from '../shared/languages';


const MAX_LABEL_LENGTH = 35;


export const getProjectLabel = (projectDocument: ResultDocument|Document): string => {

return projectDocument.resource.shortName ??
(projectDocument.resource.shortDescription
&& projectDocument.resource.shortDescription.length <= MAX_LABEL_LENGTH
? projectDocument.resource.shortDescription
const shortDescription = getTranslation(projectDocument.resource.shortDescription);
Copy link
Contributor Author

@eighttrigrams eighttrigrams Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resource.shortDescription kann leer sein.
wir müssten nochmal schauen: in getTranslation() sollte es eigentlich '' zurückgeben. kommt der code hier damit klar oder braucht er undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich lasse getTranslation jetzt immer '' zurückgeben (siehe unten in languages.ts)

const shortName = getTranslation(projectDocument.resource.shortName);

return shortName ??
(shortDescription
&& shortDescription.length <= MAX_LABEL_LENGTH
? shortDescription
: projectDocument.resource.identifier);
};
Loading