Skip to content

Commit

Permalink
Merge pull request #651 from geonetwork/multilang-search
Browse files Browse the repository at this point in the history
Multilingual search
  • Loading branch information
fgravin authored Nov 3, 2023
2 parents 48ffc34 + 020fa07 commit 7661ac3
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 34 deletions.
3 changes: 2 additions & 1 deletion conf/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ geonetwork4_api_url = "/geonetwork/srv/api"
proxy_path = ""
# This optional parameter defines, in which language metadata should be queried in elasticsearch.
# Use ISO 639-2/B (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format to indicate the language of the metadata.
# Setting to "current" will use the current language of the User Interface.
# If not indicated, a wildcard is used and no language preference is applied for the search.
# metadata_language = "fre"
# metadata_language = "current"
# This optional URL should point to the login page that allows authentication to the datahub.
# If not indicated, the default geonetwork login page is used.
# The following three placeholders can be part of this URL:
Expand Down
17 changes: 9 additions & 8 deletions libs/api/repository/src/lib/gn4/elasticsearch/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ export const ES_SOURCE_SUMMARY = [
'qualityScore',
]

export const ES_QUERY_STRING_FIELDS = [
'resourceTitleObject.${searchLang}^5',
'tag.${searchLang}^4',
'resourceAbstractObject.${searchLang}^3',
'lineageObject.${searchLang}^2',
'any.${searchLang}',
'uuid',
]
export type EsQueryFieldsPriorityType = Record<string, number>
export const ES_QUERY_FIELDS_PRIORITY = {
'resourceTitleObject.${searchLang}': 5,
'tag.${searchLang}': 4,
'resourceAbstractObject.${searchLang}': 3,
'lineageObject.${searchLang}': 2,
'any.${searchLang}': 1,
uuid: 1,
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import { ElasticsearchService } from './elasticsearch.service'
import { ES_FIXTURE_AGGS_RESPONSE } from '@geonetwork-ui/common/fixtures'
import { EsSearchParams } from '../types/elasticsearch.model'
import { LangService } from '@geonetwork-ui/util/i18n'
import { EsSearchParams } from '@geonetwork-ui/api/metadata-converter'
import { TestBed } from '@angular/core/testing'
import { METADATA_LANGUAGE } from '../../metadata-language'

class LangServiceMock {
iso3 = 'eng'
}

describe('ElasticsearchService', () => {
let service: ElasticsearchService
let searchFilters

beforeEach(() => {
service = new ElasticsearchService('fre')
TestBed.configureTestingModule({
providers: [
{
provide: LangService,
useClass: LangServiceMock,
},
{
provide: METADATA_LANGUAGE,
useValue: 'fre',
},
],
})
service = TestBed.inject(ElasticsearchService)
})

it('should be created', () => {
Expand Down Expand Up @@ -356,6 +375,72 @@ describe('ElasticsearchService', () => {
})
})

describe('#injectLangInQueryStringFields - Search language', () => {
let queryStringFields = { 'resourceTitleObject.${searchLang}': 1 }
describe('When no lang from config', () => {
beforeEach(() => {
service['metadataLang'] = undefined
})
it('use * wildcard', () => {
expect(
service['injectLangInQueryStringFields'](queryStringFields)[0].split(
'.'
)[1]
).toEqual('*')
})
})
describe('When one lang in config', () => {
beforeEach(() => {
service['metadataLang'] = 'fre'
})
it('search in the config language', () => {
expect(
service['injectLangInQueryStringFields'](queryStringFields)[0].split(
'.'
)[1]
).toEqual('langfre')
})
})
describe('When "current" language from config"', () => {
beforeEach(() => {
service['metadataLang'] = 'current'
service['lang3'] = 'eng'
})
it('search in the UI language', () => {
expect(
service['injectLangInQueryStringFields'](queryStringFields)[0].split(
'.'
)[1]
).toEqual('langeng^11')
})
it('add * fallback with low priority', () => {
queryStringFields = {
'resourceTitleObject.${searchLang}': 5,
'tag.${searchLang}': 4,
'resourceAbstractObject.${searchLang}': 3,
'lineageObject.${searchLang}': 2,
'any.${searchLang}': 1,
uuid: 1,
}
expect(
service['injectLangInQueryStringFields'](queryStringFields)
).toEqual([
'resourceTitleObject.langeng^15',
'resourceTitleObject.*^5',
'tag.langeng^14',
'tag.*^4',
'resourceAbstractObject.langeng^13',
'resourceAbstractObject.*^3',
'lineageObject.langeng^12',
'lineageObject.*^2',
'any.langeng^11',
'any.*',
'uuid',
])
})
})
})

describe('#buildAutocompletePayload', () => {
describe('given an autocomplete config', () => {
it('returns the search payload', () => {
Expand All @@ -373,9 +458,9 @@ describe('ElasticsearchService', () => {
{
multi_match: {
fields: [
'resourceTitleObject.langfre',
'resourceAbstractObject.langfre',
'tag',
'resourceTitleObject.langfre^4',
'resourceAbstractObject.langfre^3',
'tag^2',
'resourceIdentifier',
],
query: 'blarg',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Inject, Injectable, Optional } from '@angular/core'
import { Geometry } from 'geojson'
import { ES_QUERY_STRING_FIELDS, ES_SOURCE_SUMMARY } from './constant'
import {
ES_QUERY_FIELDS_PRIORITY,
ES_SOURCE_SUMMARY,
EsQueryFieldsPriorityType,
} from './constant'
import {
Aggregation,
AggregationParams,
Expand All @@ -19,6 +23,7 @@ import {
SortParams,
TermsAggregationResult,
} from '@geonetwork-ui/api/metadata-converter'
import { LangService } from '@geonetwork-ui/util/i18n'

@Injectable({
providedIn: 'root',
Expand All @@ -27,8 +32,10 @@ export class ElasticsearchService {
// runtime fields are computed using a Painless script
// see: https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html
private runtimeFields: Record<string, string> = {}
private lang3 = this.langService.iso3

constructor(
private langService: LangService,
@Optional() @Inject(METADATA_LANGUAGE) private metadataLang: string
) {}

Expand Down Expand Up @@ -168,13 +175,40 @@ export class ElasticsearchService {
}

private injectLangInQueryStringFields(
queryStringFields: string[],
lang: string
queryFieldsPriority: EsQueryFieldsPriorityType
) {
const queryLang = lang ? `lang${lang}` : `*`
return queryStringFields.map((field) => {
return field.replace(/\$\{searchLang\}/g, queryLang)
})
const queryLang = this.getQueryLang()
return Object.keys(queryFieldsPriority).reduce((query, field) => {
const multiLangRegExp = /\$\{searchLang\}/g
const isMultilangField = multiLangRegExp.test(field)
const fieldPriority = queryFieldsPriority[field]
return [
...query,
...(this.isCurrentSearchLang() && isMultilangField
? [
`${field.replace(multiLangRegExp, queryLang)}^${
fieldPriority + 10
}`,
field.replace(multiLangRegExp, '*') +
(fieldPriority > 1 ? `^${fieldPriority}` : ''),
]
: [
field.replace(multiLangRegExp, queryLang) +
(fieldPriority > 1 ? `^${fieldPriority}` : ''),
]),
]
}, [])
}

private getQueryLang(): string {
if (this.metadataLang) {
return this.isCurrentSearchLang()
? `lang${this.lang3}`
: `lang${this.metadataLang}`
} else return '*'
}
private isCurrentSearchLang() {
return this.metadataLang === 'current'
}

private buildPayloadQuery(
Expand Down Expand Up @@ -203,10 +237,7 @@ export class ElasticsearchService {
query_string: {
query: this.escapeSpecialCharacters(any),
default_operator: 'AND',
fields: this.injectLangInQueryStringFields(
ES_QUERY_STRING_FIELDS,
this.metadataLang
),
fields: this.injectLangInQueryStringFields(ES_QUERY_FIELDS_PRIORITY),
},
})
}
Expand Down Expand Up @@ -290,15 +321,12 @@ export class ElasticsearchService {
multi_match: {
query,
type: 'bool_prefix',
fields: this.injectLangInQueryStringFields(
[
'resourceTitleObject.${searchLang}',
'resourceAbstractObject.${searchLang}',
'tag',
'resourceIdentifier',
],
this.metadataLang
),
fields: this.injectLangInQueryStringFields({
'resourceTitleObject.${searchLang}': 4,
'resourceAbstractObject.${searchLang}': 3,
tag: 2,
resourceIdentifier: 1,
}),
},
},
],
Expand Down

0 comments on commit 7661ac3

Please sign in to comment.