diff --git a/IsraelHiking.API/Controllers/OsmTracesController.cs b/IsraelHiking.API/Controllers/OsmTracesController.cs index 3a159f17..a4c1f5f3 100644 --- a/IsraelHiking.API/Controllers/OsmTracesController.cs +++ b/IsraelHiking.API/Controllers/OsmTracesController.cs @@ -171,10 +171,7 @@ private async Task GetDescriptionByArea(string language, List diff --git a/IsraelHiking.API/Executors/FeaturesMergeExecutor.cs b/IsraelHiking.API/Executors/FeaturesMergeExecutor.cs index c1f42808..9f397310 100644 --- a/IsraelHiking.API/Executors/FeaturesMergeExecutor.cs +++ b/IsraelHiking.API/Executors/FeaturesMergeExecutor.cs @@ -649,8 +649,8 @@ private void AddAlternativeTitleToNatureReserves(List features) do { index++; - } while (natureReserveFeature.Attributes.Exists(FeatureAttributes.NAME + ":he" + index)); - natureReserveFeature.Attributes.Add(FeatureAttributes.NAME + ":he" + index, alternativeTitle); + } while (natureReserveFeature.Attributes.Exists(FeatureAttributes.NAME + ":" + Languages.HEBREW + index)); + natureReserveFeature.Attributes.Add(FeatureAttributes.NAME + ":" + Languages.HEBREW + index, alternativeTitle); } natureReserveFeature.SetTitles(); } diff --git a/IsraelHiking.Common/Strings.cs b/IsraelHiking.Common/Strings.cs index 5d4ad7a2..fed9d6d5 100644 --- a/IsraelHiking.Common/Strings.cs +++ b/IsraelHiking.Common/Strings.cs @@ -1,4 +1,6 @@ -namespace IsraelHiking.Common; +using System.Linq; + +namespace IsraelHiking.Common; public static class Categories { @@ -105,11 +107,15 @@ public static class Languages public const string ALL = "all"; public const string HEBREW = "he"; public const string ENGLISH = "en"; + public const string RUSSIAN = "ru"; + public const string DEFAULT = "default"; public static readonly string[] Array = [ HEBREW, - ENGLISH + ENGLISH, + RUSSIAN ]; + public static readonly string[] ArrayWithDefault = new [] { DEFAULT }.Concat(Array).ToArray(); } public static class Branding diff --git a/IsraelHiking.DataAccess/ElasticSearch/ElasticSearchGateway.cs b/IsraelHiking.DataAccess/ElasticSearch/ElasticSearchGateway.cs index d34d11df..08ae2373 100644 --- a/IsraelHiking.DataAccess/ElasticSearch/ElasticSearchGateway.cs +++ b/IsraelHiking.DataAccess/ElasticSearch/ElasticSearchGateway.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Elasticsearch.Net; using IsraelHiking.Common; @@ -123,16 +124,63 @@ private List> GetAllItemsByScrolling(ISearchResponse response) whe return list; } + /// + /// This method is used to extract the field with the highest contribution to the score + /// It uses the explanation object to recursively find the field + /// + /// + /// + private string FindBestField(ExplanationDetail exp) + { + // Extract the field from the explanation + var match = Regex.Match(exp.Description, @"weight\((.*)\)"); + if (match.Success) + { + return match.Groups[1].Value; + } + + // Recursively check child explanations + foreach (var child in exp.Details) + { + var childResult = FindBestField(child); + if (!string.IsNullOrEmpty(childResult)) + return childResult; + } + + return null; + } + + /// + /// This method is used to find the language with the highest contribution to the score + /// + /// The explanination object from the query results + /// The language to use in case no matching language was found + /// + private string GetBestMatchLanguage(Explanation explanation, string fallbackLanguage) + { + // Recursive method to find the field with the highest contribution to the score + foreach (var details in explanation.Details) + { + var results = FindBestField(details); + if (!string.IsNullOrEmpty(results) && Languages.ArrayWithDefault.Any(l => results.Contains("." + l))) + { + return Languages.ArrayWithDefault.First(l => results.Contains("." + l)); + } + } + return fallbackLanguage; + } + private IFeature HitToFeature(IHit d, string language) { + var searchTermLanguage = GetBestMatchLanguage(d.Explanation, language); IFeature feature = new Feature(new Point(d.Source.Location[0], d.Source.Location[1]), new AttributesTable { - { FeatureAttributes.NAME, d.Source.Name.GetValueOrDefault(language, d.Source.Name.GetValueOrDefault(Languages.ENGLISH, string.Empty)) }, + { FeatureAttributes.NAME, d.Source.Name.GetValueOrDefault(searchTermLanguage, d.Source.Name.GetValueOrDefault(Languages.ENGLISH, string.Empty)) }, { FeatureAttributes.POI_SOURCE, d.Source.PoiSource }, { FeatureAttributes.POI_ICON, d.Source.PoiIcon }, { FeatureAttributes.POI_CATEGORY, d.Source.PoiCategory }, { FeatureAttributes.POI_ICON_COLOR, d.Source.PoiIconColor }, - { FeatureAttributes.DESCRIPTION, d.Source.Description.GetValueOrDefault(language, d.Source.Description.GetValueOrDefault(Languages.ENGLISH, string.Empty)) }, + { FeatureAttributes.DESCRIPTION, d.Source.Description.GetValueOrDefault(searchTermLanguage, d.Source.Description.GetValueOrDefault(Languages.ENGLISH, string.Empty)) }, { FeatureAttributes.POI_ID, d.Id }, { FeatureAttributes.POI_LANGUAGE, Languages.ALL }, { FeatureAttributes.ID, string.Join("_", d.Id.Split("_").Skip(1)) } @@ -146,19 +194,22 @@ private IFeature HitToFeature(IHit d, string language) return feature; } - private QueryContainer DocumentNameSearchQuery(QueryContainerDescriptor q, string searchTerm, string language) where T: class + private QueryContainer DocumentNameSearchQuery(QueryContainerDescriptor q, string searchTerm) where T: class { return q.DisMax(dm => dm.Queries(sh => - sh.MatchPhrase(m => - m.Query(searchTerm) - .Field("name." + language + ".keyword") - .Boost(5) + sh.MultiMatch(m => + m.Type(TextQueryType.Phrase) + .Query(searchTerm) + .Boost(5) + .Fields(f => f.Fields(Languages.ArrayWithDefault.Select(l => new Field("name." + l + ".keyword")))) ), - sh => sh.Match(m => - m.Query(searchTerm) - .Field("name." + language) + sh => + sh.MultiMatch(m => + m.Type(TextQueryType.BestFields) + .Query(searchTerm) .Fuzziness(Fuzziness.Auto) + .Fields(f => f.Fields(Languages.ArrayWithDefault.Select(l => new Field("name." + l)))) ) ) ); @@ -175,7 +226,8 @@ public async Task> Search(string searchTerm, string language) .Size(NUMBER_OF_RESULTS) .TrackScores() .Sort(f => f.Descending("_score")) - .Query(q => DocumentNameSearchQuery(q, searchTerm, language)) + .Query(q => DocumentNameSearchQuery(q, searchTerm)) + .Explain() ); return response.Hits.Select(d=> HitToFeature(d, language)).ToList(); } @@ -192,10 +244,12 @@ public async Task> SearchExact(string searchTerm, string language .TrackScores() .Sort(f => f.Descending("_score")) .Query(q => - q.MatchPhrase(m => - m.Query(searchTerm) - .Field("name." + language + ".keyword")) + q.MultiMatch(m => + m.Type(TextQueryType.Phrase) + .Query(searchTerm) + .Fields(f => f.Fields(Languages.ArrayWithDefault.Select(l => new Field("name." + l + ".keyword")))) ) + ).Explain() ); return response.Hits.Select(d=> HitToFeature(d, language)).ToList(); } @@ -213,7 +267,7 @@ public async Task> SearchPlaces(string searchTerm, string languag .Size(1) .TrackScores() .Sort(f => f.Descending("_score")) - .Query(q => DocumentNameSearchQuery(q, place, language)) + .Query(q => DocumentNameSearchQuery(q, place)) ); if (placesResponse.Documents.Count == 0) { @@ -223,7 +277,7 @@ public async Task> SearchPlaces(string searchTerm, string languag .Size(NUMBER_OF_RESULTS) .TrackScores() .Sort(f => f.Descending("_score")) - .Query(q => DocumentNameSearchQuery(q, searchTerm, language) && + .Query(q => DocumentNameSearchQuery(q, searchTerm) && q.GeoShape(b => { b.Field(p => p.Location); diff --git a/IsraelHiking.Web/src/application/components/dialogs/intro-dialog.component.html b/IsraelHiking.Web/src/application/components/dialogs/intro-dialog.component.html index 694fb55f..cc364c4d 100644 --- a/IsraelHiking.Web/src/application/components/dialogs/intro-dialog.component.html +++ b/IsraelHiking.Web/src/application/components/dialogs/intro-dialog.component.html @@ -1,10 +1,9 @@
-

{{resources.language}}

- {{resources.getLabelForCode(language.code)}} + {{language.label}}

diff --git a/IsraelHiking.Web/src/application/components/dialogs/language-dialog.component.html b/IsraelHiking.Web/src/application/components/dialogs/language-dialog.component.html index 9d6f782e..fe42b8e3 100644 --- a/IsraelHiking.Web/src/application/components/dialogs/language-dialog.component.html +++ b/IsraelHiking.Web/src/application/components/dialogs/language-dialog.component.html @@ -6,7 +6,7 @@
- {{resources.getLabelForCode(language.code)}} + {{language.label}}

diff --git a/IsraelHiking.Web/src/application/components/main-menu.component.html b/IsraelHiking.Web/src/application/components/main-menu.component.html index 17ff7758..0848b6f7 100644 --- a/IsraelHiking.Web/src/application/components/main-menu.component.html +++ b/IsraelHiking.Web/src/application/components/main-menu.component.html @@ -75,7 +75,7 @@ diff --git a/IsraelHiking.Web/src/application/components/search.component.ts b/IsraelHiking.Web/src/application/components/search.component.ts index 6f18d602..013444d7 100644 --- a/IsraelHiking.Web/src/application/components/search.component.ts +++ b/IsraelHiking.Web/src/application/components/search.component.ts @@ -275,7 +275,7 @@ export class SearchComponent { searchTerm } as SearchRequestQueueItem); try { - const results = await this.searchResultsProvider.getResults(searchTerm, this.resources.hasRtlCharacters(searchTerm)); + const results = await this.searchResultsProvider.getResults(searchTerm); const queueItem = this.requestsQueue.find(itemToFind => itemToFind.searchTerm === searchTerm); if (queueItem == null || this.requestsQueue.indexOf(queueItem) !== this.requestsQueue.length - 1) { this.requestsQueue.splice(0, this.requestsQueue.length - 1); diff --git a/IsraelHiking.Web/src/application/models/language.d.ts b/IsraelHiking.Web/src/application/models/language.d.ts index f03045db..b0d1cb49 100644 --- a/IsraelHiking.Web/src/application/models/language.d.ts +++ b/IsraelHiking.Web/src/application/models/language.d.ts @@ -1,6 +1,7 @@ -export type LanguageCode = "en-US" | "he"; +export type LanguageCode = "en-US" | "he" | "ru"; export type Language = { code: LanguageCode; rtl: boolean; + label: string; }; diff --git a/IsraelHiking.Web/src/application/reducers/initial-state.ts b/IsraelHiking.Web/src/application/reducers/initial-state.ts index 9b232ea1..8fe98109 100644 --- a/IsraelHiking.Web/src/application/reducers/initial-state.ts +++ b/IsraelHiking.Web/src/application/reducers/initial-state.ts @@ -15,10 +15,17 @@ export const SPECIAL_LAYERS = [...SPECIAL_BASELAYERS, ...SPECIAL_OVERLAYS]; export const AVAILABLE_LANGUAGES: Language[] = [{ code: "he", rtl: true, + label: "עברית" }, { code: "en-US", rtl: false, + label: "English" + }, + { + code: "ru", + rtl: false, + label: "Русский" } ]; diff --git a/IsraelHiking.Web/src/application/services/image-gallery.service.ts b/IsraelHiking.Web/src/application/services/image-gallery.service.ts index 329dc342..e88ea9e1 100644 --- a/IsraelHiking.Web/src/application/services/image-gallery.service.ts +++ b/IsraelHiking.Web/src/application/services/image-gallery.service.ts @@ -13,7 +13,7 @@ export class ImageGalleryService { private readonly resourcesService = inject(ResourcesService); public open(urls: string[], index: number) { - if (this.resourcesService.getCurrentLanguageCodeSimplified() === "he") { + if (this.resourcesService.direction === "rtl") { urls = [...urls].reverse(); index = urls.length - 1 - index; } diff --git a/IsraelHiking.Web/src/application/services/poi.service.spec.ts b/IsraelHiking.Web/src/application/services/poi.service.spec.ts index b6b43560..faa046ca 100644 --- a/IsraelHiking.Web/src/application/services/poi.service.spec.ts +++ b/IsraelHiking.Web/src/application/services/poi.service.spec.ts @@ -205,7 +205,7 @@ describe("Poi Service", () => { } ] as any; - store.dispatch(new SetLanguageAction({ code: "he", rtl: false })); + store.dispatch(new SetLanguageAction({ code: "he", rtl: false, label: "עברית" })); await new Promise((resolve) => setTimeout(resolve, 100)); // this is in order to let the code continue to run to the next await diff --git a/IsraelHiking.Web/src/application/services/resources.service.spec.ts b/IsraelHiking.Web/src/application/services/resources.service.spec.ts index bb4a1ecc..8d1e0220 100644 --- a/IsraelHiking.Web/src/application/services/resources.service.spec.ts +++ b/IsraelHiking.Web/src/application/services/resources.service.spec.ts @@ -53,6 +53,7 @@ describe("ResourcesService", () => { expect(service.hasRtlCharacters("1. نص عربي")).toBeTruthy(); expect(service.hasRtlCharacters("hello")).toBeFalsy(); expect(service.hasRtlCharacters("1. hello")).toBeFalsy(); + expect(service.hasRtlCharacters("1. Кейсария")).toBeFalsy(); })); it("Should be able get the layout direction for titles", inject([ResourcesService], (service: ResourcesService) => { diff --git a/IsraelHiking.Web/src/application/services/resources.service.ts b/IsraelHiking.Web/src/application/services/resources.service.ts index c414524a..43cf0d40 100644 --- a/IsraelHiking.Web/src/application/services/resources.service.ts +++ b/IsraelHiking.Web/src/application/services/resources.service.ts @@ -951,10 +951,6 @@ export class ResourcesService { await this.setLanguageInternal(language); } - public getLabelForCode(code: LanguageCode): string { - return code === "he" ? "עברית" : "English"; - } - public translate(word: string): string { return this.gettextCatalog.getString(word) || word; } diff --git a/IsraelHiking.Web/src/application/services/search-results.provider.spec.ts b/IsraelHiking.Web/src/application/services/search-results.provider.spec.ts index 2f75c68f..d8a27450 100644 --- a/IsraelHiking.Web/src/application/services/search-results.provider.spec.ts +++ b/IsraelHiking.Web/src/application/services/search-results.provider.spec.ts @@ -7,6 +7,7 @@ import { GeoJsonParser } from "./geojson.parser"; import { RunningContextService } from "./running-context.service"; import { PoiService } from "./poi.service"; import { CoordinatesService } from "./coordinates.service"; +import { ResourcesService } from "./resources.service"; import type { SearchResultsPointOfInterest } from "../models/models"; describe("SearchResultsProvider", () => { @@ -18,6 +19,9 @@ describe("SearchResultsProvider", () => { CoordinatesService, { provide: RunningContextService, useValue: { isOnline: true } }, { provide: PoiService, useValue: null }, + { provide: ResourcesService, useValue: { + getCurrentLanguageCodeSimplified: () => "en" + } }, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting() ] @@ -26,7 +30,7 @@ describe("SearchResultsProvider", () => { it("Should get all kind of features in results", (inject([SearchResultsProvider, HttpTestingController], async (provider: SearchResultsProvider, mockBackend: HttpTestingController) => { - const promise = provider.getResults("searchTerm", true).then((results: SearchResultsPointOfInterest[]) => { + const promise = provider.getResults("searchTerm").then((results: SearchResultsPointOfInterest[]) => { expect(results.length).toBe(1); }, fail); diff --git a/IsraelHiking.Web/src/application/services/search-results.provider.ts b/IsraelHiking.Web/src/application/services/search-results.provider.ts index a19eea30..edf49b71 100644 --- a/IsraelHiking.Web/src/application/services/search-results.provider.ts +++ b/IsraelHiking.Web/src/application/services/search-results.provider.ts @@ -5,6 +5,7 @@ import { firstValueFrom } from "rxjs"; import { CoordinatesService } from "./coordinates.service"; import { RouteStrings, getIdFromLatLng } from "./hash.service"; +import { ResourcesService } from "./resources.service"; import { Urls } from "../urls"; import type { SearchResultsPointOfInterest } from "../models/models"; @@ -13,8 +14,9 @@ export class SearchResultsProvider { private readonly httpClient = inject(HttpClient); private readonly coordinatesService = inject(CoordinatesService); + private readonly resources = inject(ResourcesService) - public async getResults(searchTerm: string, isHebrew: boolean): Promise { + public async getResults(searchTerm: string): Promise { const searchWithoutBadCharacters = searchTerm.replace("/", " ").replace("\t", " "); const latlng = this.coordinatesService.parseCoordinates(searchWithoutBadCharacters); if (latlng) { @@ -30,8 +32,7 @@ export class SearchResultsProvider { description: "", }]; } - const language = isHebrew ? "he" : "en"; - const params = new HttpParams().set("language", language); + const params = new HttpParams().set("language", this.resources.getCurrentLanguageCodeSimplified()); const response = await firstValueFrom(this.httpClient.get(Urls.search + encodeURIComponent(searchWithoutBadCharacters), { params }).pipe(timeout(3000))); diff --git a/IsraelHiking.Web/src/translations/he.json b/IsraelHiking.Web/src/translations/he.json index abfd6f25..ea8c6497 100644 --- a/IsraelHiking.Web/src/translations/he.json +++ b/IsraelHiking.Web/src/translations/he.json @@ -181,7 +181,7 @@ "km per hour": "קמ\"ש", "Km POIs": "נ\"צ לק\"מ", "Lake, Reservoir": "מקווה מים", - "Language": "שפה - Language", + "Language": "שפה", "Last recording did not end well. Feel free to start a new one.": "ההקלטה האחרונה לא הסתיימה בהצלחה. אתם מוזמנים להתחיל הקלטה חדשה.", "Last updated on": "עודכן לאחרונה ב- ", "Layers": "מסלולים - מפות - שכבות", diff --git a/IsraelHiking.Web/src/translations/ru.json b/IsraelHiking.Web/src/translations/ru.json new file mode 100644 index 00000000..52f18175 --- /dev/null +++ b/IsraelHiking.Web/src/translations/ru.json @@ -0,0 +1,427 @@ +{ + "4x4 Routing": "4x4 Маршрутизация", + "A few words about what you are sharing.": "Несколько слов о том, что вы сохраняете.", + "A link to a website": "Ссылка на сайт", + "A name to be displayed in the layers controller": "Имя, которое будет отображаться в списке карт или слоев", + "About": "О нас", + "Add Base Layer": "Добавить базовый слой", + "Add link": "Добавить ссылку", + "Add Overlay": "Добавить наложение", + "Add point to active route": "Добавить точку в активный маршрут", + "Add Point to Route": "Добавить точку в маршрут", + "Add Route": "Добавить маршрут", + "Add This Route to OSM": "Добавьте этот маршрут в OSM", + "Add to routes": "Добавить к маршрутам", + "Address": "Адрес", + "Advanced": "Расширенные", + "Advanced Settings": "Расширенные настройки", + "Aerialway": "Канатная дорога", + "All files are up-to-date :-)": "Все файлы обновлены 😊", + "All Vehicles": "Все Транспортные Средства", + "Allow turning off the screen": "Разрешить отключение экрана", + "Allows you to automatically upload a recorded route when you finish recording": "Позволяет автоматически загружать записанный маршрут после завершения записи", + "Amenities": "Удобства", + "And So Much More!": "И многое другое!", + "Antenna": "Антенна", + "Application": "Приложение", + "Archaeological Site": "Археологический объект", + "Are you sure you want to delete all routes?": "Вы уверены, что хотите удалить все маршруты?", + "Are you sure you want to stop the current recording?": "Вы уверены, что хотите остановить текущую запись?", + "Are you sure?": "Вы уверены?", + "Area A": "Область A", + "Area B": "Область B", + "Areas": "Области", + "Asks you to classify missing routes on the map after you upload a recording": "Просит вас классифицировать отсутствующие маршруты на карте после загрузки записи", + "Attraction": "Достопримечательность", + "Attribution": "Атрибуция", + "Automatic upload of recording": "Автоматическая загрузка записи", + "Average speed": "Средняя скорость", + "Back": "Назад", + "Background text: You need to download offline maps": "Необходимо загрузить офлайн карты.", + "Background text: You need to purchase offline maps": "Рекомендуется приобрести карты для использования офлайн.", + "Background text: You need to toggle offline maps": "Чтобы просмотреть офлайн-карты, переключитесь на нужную карту.", + "Barriers": "Препятствия", + "Base layer and overlay are overlapping.": "Базовый слой и наложение перекрываются.", + "Base Layer Properties": "Свойства базового слоя", + "Base Layers": "Базовые карты", + "Battery optimization": "Оптимизация батареи", + "Beach": "Пляж", + "Bicycle": "Велосипед", + "Bicycle Path": "Велосипедная дорожка", + "Bicycle Trails": "Велосипедные пути", + "Bike Park": "Велопарк", + "Bike Routing": "Маршрутизация велосипедов", + "Bike Shop": "Магазин велосипедов", + "Black Marked Trail": "Чёрная отмеченная тропа", + "Block": "Блок", + "Blue Marked Trail": "Синяя отмеченная тропа", + "Borders": "Границы и Особые Области", + "Bridge": "Мост", + "Café": "Кафе", + "Camera": "Камера", + "Camping": "Кемпинг", + "Campsite": "Кемпинг", + "Cancel": "Отмена", + "Categories": "Категории", + "Cattle Grid": "Табличная сетка", + "Cave": "Пещера", + "Cemetery": "Кладбище", + "Center Me": "Центрируйте меня", + "Challenging with Direction": "Сложный маршрут", + "Church": "Церковь", + "Cistern": "Цистерна", + "City, Settlement": "Город, Поселение", + "Clear": "Очистить", + "Clear Both": "Очистить оба", + "Clear Points": "Очистить точки", + "Clear Route": "Очистить маршрут", + "Click back again to close the app": "Нажмите назад, чтобы закрыть приложение", + "Cliff": "Утёс", + "Close": "Закрыть", + "Closed Gate": "Закрытые ворота", + "Construction Site": "Строительная площадка", + "Continue": "Продолжить", + "Convenience Store": "Магазин удобств", + "Convert to Route": "Преобразовать в маршрут", + "Copy Link": "Копировать ссылку", + "Create new hike in Nakeb": "Создать новый маршрут в Nakeb", + "Create Share": "Сохранить в облаке", + "Crop": "Обрезать", + "Current speed": "Текущая скорость", + "Darken the screen": "Затемнить экран", + "Delete": "Удалить", + "Delete Account": "Удалить аккаунт", + "Delete All Routes": "Удалить все маршруты", + "Delete Layer": "Удалить слой", + "Delete POI": "Удалить точку интереса", + "Delete Route": "Удалить маршрут", + "Deletion of": "Удаление", + "Description": "Описание", + "Description in {{translation language}}": "Описание на Русском", + "Details Level": "Уровень деталей", + "Difficult 4WD": "Сложный 4WD", + "Dims display when there's no user interaction": "Математика затемняется, если нет взаимодействия с пользователем", + "Directional Search": "Направленный поиск", + "Dirt Road": "Грунтовая дорога", + "Distance": "Расстояние", + "Distance (Km)": "Расстояние (км)", + "Don't show this message again": "Не показывать это сообщение снова", + "Download finished successfully!": "Загрузка завершена успешно!", + "Download Map for Offline Use": "Загрузите карту для офлайн-использования", + "Download old raster maps for offline use in OruxMaps and Locus": "Скачайте старые растровые карты для офлайн-использования в OruxMaps и Locus", + "Downloading points of interest for offline usage...": "Загрузка точек интереса для офлайн-использования...", + "Drinking Water": "Питьевая вода", + "Duration": "Продолжительность", + "Easy with Direction": "Легкий", + "Edit": "Редактировать", + "Edit POI": "Редактировать точку интереса", + "Edit Route": "Редактировать маршрут", + "Edit Route Details": "Редактировать детали маршрута", + "Edit This Map Using OSM": "Редактировать эту карту в OSM", + "Elaborated terms of service of this site, OSM and wikimedia": "Условия использования этого сайта, OSM и Викимедиа.", + "Elevation": "Высота", + "Explanation on how to open Facebook link outside facebook": "Обратите внимание! Вы сейчас находитесь на сайте внутри приложения Facebook.", + "Export": "Экспорт", + "Export As...": "Экспорт как...", + "F.A.Q": "Часто задаваемые вопросы", + "Fence": "Забор", + "File uploaded successfully, It will take some time to add it to OSM database.": "Файл успешно загружен, потребуется время для добавления его в базу данных OSM.", + "Files": "Файлы", + "Find missing routes after upload": "Найти отсутствующие маршруты после загрузки", + "Find Unmapped Routes": "Найти немаршрутизированные пути", + "Finished opening file! :-)": "Файл успешно открыт! 😊", + "First Aid": "Первая помощь", + "Flowers": "Цветы", + "Foot Path": "Пешеходная тропа", + "Fuel Station": "Заправочная станция", + "Gain": "Набор высоты", + "Gallery": "Галерея", + "Gate": "Проход", + "Generate A URL To Share With Your Friends!": "Сгенерируйте URL для совместного использования с друзьями!", + "Generate markers for all route points": "Создать маркеры для всех точек маршрута", + "Golan Trail": "Голанский путь", + "Got lost warnings": "Предупреждения о потерянной связи", + "GPS tracking is enabled while editing, in order to avoid map centering to current location please click the cross icon on the top left corner": "Отслеживание GPS включено во время редактирования.", + "Grass": "Трава", + "Green Marked Trail": "Зеленая отмеченная тропа", + "Guidepost": "Указатель", + "Haifa Wadis Trail": "Тропа Вадис Хайфа", + "Harel, Zeev and Guy": "Харэль, Зив и Гай", + "Height": "Высота", + "Height (m)": "Высота (м)", + "Helpful links:": "Полезные ссылки:", + "Hidden routes will not be saved...": "Скрытые маршруты не будут сохранены...", + "Hike Routing": "ТД маршрутизация", + "Hiking": "Походы", + "Hiking Trails": "Тропы для походов", + "Historic": "Исторический", + "Holy Place": "Святое место", + "hr": "ч.", + "I have read and agree to the terms": "Я прочитал и согласен с условиями", + "Image by": "Изображение предоставлено", + "Imgur terms of service": "Условия использования Imgur", + "Information Center": "Информационный центр", + "Installation Instructions": "Инструкции по установке", + "Installation instructions for Locus on desktop - surround each new line with
  • ": "Инструкции по установке Locus на настольном ПК.", + "Installation instructions for OruxMaps on desktop - surround each new line with
  • ": "Инструкции по установке OruxMaps на настольном ПК.", + "Interact with other users in our Facebook group": "Взаимодействуйте с другими пользователями в нашей группе Facebook", + "International Border": "Международная граница", + "Intro dialog description for 'so much more'": "Ищите наши обучающие материалы, страницу часто задаваемых вопросов, легенду и другие классные функции!", + "Intro dialog description for maps": "Картографические маршруты, карты для горных велосипедов и спутниковые изображения.", + "Intro dialog description for routes and points": "Планируйте и делитесь маршрутами, просматривайте и фильтруйте точки интереса.", + "Israel Hiking Map": "Карта пешеходного туризма в Израиле", + "Israel MTB Map": "Карта горных велосипедов в Израиле", + "Israel Trail": "Израильская тропа", + "Jammed position received...": "Получено поврежденное местоположение...", + "Jerusalem Trail": "Иерусалимская тропа", + "Keep screen on": "Держите экран включенным", + "Kinneret Bicycle Trail": "Велосипедная тропа на Киннерете", + "Kinneret Trail": "Киннеретская тропа", + "Km": "Км", + "km per hour": "км/ч", + "Km POIs": "Точки интереса на км", + "Lake, Reservoir": "Озеро, водохранилище", + "Language": "Язык", + "Last recording did not end well. Feel free to start a new one.": "Последняя запись не завершилась удачно.", + "Last updated on": "Последнее обновление", + "Layers": "Слои", + "Learn Israel-specific mapping rules at the Israel OSM Wiki Project": "Изучите правила картографии, специфичные для Израиля.", + "Legend": "Легенда", + "Length": "Длина", + "Less...": "Меньше...", + "Lets you know when the your planned route is more than 50 meters from your current position": "Уведомляет вас, когда ваш запланированный маршрут находится более чем на 50 метров от вашего текущего местоположения", + "Light 4WD Vehicles": "Легкие внедорожники", + "Local": "Местный", + "Local Trail": "Местная тропа", + "Lodging": "Размещение", + "Login": "Вход", + "Login token expired, please login again": "Срок действия токена входа истек, пожалуйста, войдите снова", + "Logout": "Выход", + "Long press on any button will shows its usage": "Долгий нажатие на любую кнопку покажет ее использование", + "Loss": "Потеря", + "Low-Speed Street": "Улица с ограничением скорости", + "m": "м", + "Manage subscriptions": "Управление подписками", + "Map": "Карта", + "Maps": "Карты", + "Marked Trails": "Отмеченные тропы", + "Max Zoom": "Максимальный зум", + "Measure distance from current location": "Измерьте расстояние от текущего местоположения", + "Memorial": "Памятник", + "Merge": "Объединить", + "Military Area": "Военная зона", + "Military Training": "Обучение военнослужащих", + "min": "мин", + "Min Zoom": "Минимальный зум", + "Minefield": "Минное поле", + "Moderate": "Умеренный", + "More Info...": "Больше информации...", + "More map addresses can be found here, look for TMS": "Дополнительные адреса карт можно найти здесь.", + "More...": "Больше...", + "Mosque": "Мечеть", + "Motorway": "Автомагистраль", + "Move to Route": "Переместить на маршрут", + "My Shares": "Мои сохранения в облаке", + "My Traces": "Мои следы", + "Name": "Имя", + "Name in {{translation language}}": "Имя на Русском", + "National Trail": "Национальная тропа", + "Natural": "Натуральный", + "Nature Reserve, National Park": "Природный резерват, Национальный парк", + "Navigate Here": "Навигация сюда", + "Navigate with Waze": "Навигация с помощью Waze", + "New version available, do you want to update?": "Доступна новая версия, хотите обновить?", + "Next": "Следующий", + "No": "Нет", + "No legend for this map...": "Нет легенды для этой карты...", + "No offline files available, please press the download button below.": "Нет доступных файлов офлайн, пожалуйста, нажмите кнопку загрузки ниже.", + "No shares, now is the time to start sharing your work!": "Нет сохранений, сейчас время начать делиться вашей работой!", + "No Tags": "Нет тегов", + "No traces, you should really upload some to OSM.": "Нет следов, вам стоит действительно загрузить некоторые в OSM.", + "No unmapped routes! :-)": "Нет немаршрутизированных путей! 😊", + "Non-Marked Trail": "Неотмеченная тропа", + "North-Up": "Север вверх", + "Not yet...": "Ещё не...", + "Observation Tower": "Смотровая башня", + "OK": "ОК", + "Oops, something went wrong. Please try again later": "Упс, что-то пошло не так. Пожалуйста, попробуйте позже", + "Opacity": "Прозрачность", + "Open a File": "Открыть файл", + "Open in a new window": "Открыть в новом окне", + "Open in App": "Открыть в приложении", + "Opening file, this might take a while, please don't close the app...": "Открытие файла, это может занять некоторое время, пожалуйста, не закрывайте приложение...", + "Orange Regional Trail": "Оранжевая региональная тропа", + "Orchard": "Сад", + "OSM terms of service": "Условия обслуживания OSM", + "Other": "Другое", + "Overlay Properties": "Свойства наложения", + "Overlays": "Наложения", + "Parking": "Парковка", + "Path": "Дорога", + "Paved Road": "Асфальтированная дорога", + "Peak": "Вершина", + "Picnic Area": "منطقة نزهة", + "Playground": "Игровая площадка", + "Please add points to route...": "Пожалуйста, добавьте точки к маршруту...", + "Please fill in the details of the issue in the e-mail message that will be shown soon and send it": "Пожалуйста, заполните детали проблемы в электронной почте, которая будет показана вскоре, и отправьте её", + "Please make sure the battery optimization is turned off for this application. Go to application setting to do so.": "Пожалуйста, убедитесь, что оптимизация батареи выключена для этого приложения. Перейдите в настройки приложения, чтобы сделать это.", + "Please select from...": "Пожалуйста, выберите из...", + "Please select to...": "Пожалуйста, выберите для...", + "Points of Interest": "Точки интереса", + "Popularity Heatmap": "Тепловая карта популярности", + "Power Line": "Линия электропередач", + "Preparing data for issue report": "Подготовка данных для отчета о проблеме", + "Present share": "Представить сохранение", + "Primary": "Первичный", + "Privacy Policy and Terms of Service": "Политика конфиденциальности и условия обслуживания", + "Private": "Частный", + "Private Routes": "Частные маршруты", + "Public": "Общественный", + "Purchase Maps": "Купить карты", + "Purchase maps for offline use": "Купить карты для использования вне сети", + "Purple Regional Trail": "Фиолетовая региональная тропа", + "Quarry": "Карьер", + "Railway": "Железная дорога", + "Railway Station": "Железнодорожная станция", + "Railway Tunnel": "Железнодорожный туннель", + "Red Marked Trail": "Красная отмеченная тропа", + "Regional Trail": "Региональная тропа", + "Regional Trails": "Региональные тропы", + "Remaining distance": "Оставшееся расстояние", + "Renew offline maps subscription": "Обновить подписку на офлайн-карты", + "Report an issue": "Сообщить о проблеме", + "Report an issue instructions": "Пожалуйста, опишите проблему как можно лучше и приложите скриншоты, которые её показывают.", + "Report an issue site instructions": "Файл отчета только что был загружен. Нам нужен этот файл, чтобы лучше понять проблему.", + "Request features and report bugs on our Github project page": "Запросите функции и сообщите об ошибках на нашей странице проекта на GitHub", + "Reset Data": "Сбросить данные", + "Residential": "Жилой", + "Restaurant": "Ресторан", + "Reverse Route": "Разворот маршрута", + "River": "Река", + "Roads": "Дороги", + "Route": "Маршрут", + "Route added successfully, It will take some time for the map to update.": "Маршрут успешно добавлен, потребуется время для обновления карты.", + "Route is hidden...": "Маршрут скрыт...", + "Route Planning": "Планирование маршрута", + "Route Properties": "Свойства маршрута", + "Route Statistics": "Статистика маршрута", + "Routes": "Маршруты", + "Routes and Points": "Маршруты и точки", + "Routing failed, consider buying a subscription.": "Маршрутизация не удалась, подумайте о покупке подписки.", + "Routing failed, please try a shorter route...": "Маршрутизация не удалась, пожалуйста, попробуйте более короткий маршрут...", + "Ruins": "Развалины", + "Running in the background": "Работа в фоновом режиме", + "Runway and Taxiway": "Взлетно-посадочная полоса и рулежная дорожка", + "Sand": "Песок", + "Satellite Imagery": "Спутниковые изображения", + "Save": "Сохранить", + "Save Route to File": "Экспортировать маршрут", + "Scrub": "Кустарник", + "Search": "Поиск", + "Seasonal Lake, Reservoir, or Riverbed": "Сезонное озеро или водохранилище, пересохшее русло реки", + "Secondary": "Вторичный", + "See Also": "Смотрите также", + "Select Icon": "Выберите иконку", + "Share": "Поделиться", + "Share Location": "Поделиться местоположением", + "Share maps overlays": "Включить наложения карт", + "Share With Facebook": "Поделиться через Facebook", + "Share With WhatsApp": "Поделиться через WhatsApp", + "Show Coordinates": "Показать координаты", + "Show Me Where I am": "Показать, где я нахожусь", + "Show Slopes": "Показать уклоны", + "Singles": "Одноразовые маршруты", + "Slope": "Уклон", + "Split": "Разделить", + "Spring": "Исток", + "Spring, Pond": "Источник, пруд", + "Start Download": "Начать загрузку", + "Statistics and Height Chart": "Статистика и диаграмма высоты", + "Steps": "Шаги", + "Straight Lines": "Прямые линии", + "Stream": "Ручей", + "Strong 4WD Vehicles": "Сложные внедорожные машины", + "Submit": "Отправить", + "Synagogue": "Синагога", + "Tags": "Теги", + "Terms of Service": "Условия обслуживания", + "Tertiary": "Третичный", + "Thank you for your support!": "Спасибо за вашу поддержку!", + "Thanks for purchasing! download instructions here...": "Спасибо за покупку! инструкции по скачиванию здесь...", + "The data was updated successfully!": "Данные успешно обновлены!", + "The data was updated successfully! It will take time to see it on the map...": "Данные успешно обновлены! Потребуется время, чтобы увидеть это на карте...", + "The download may take several minutes, and afterwards you can enjoy the map with no need for a network connection.": "Загрузка может занять несколько минут, и потом вы можете наслаждаться картой без подключения к сети.", + "The Green Line": "Зеленая линия", + "The offline database has been upgraded...": "Офлайн база данных была обновлена...", + "The Purple Line": "Пурпурная линия", + "The route's name was altered since it is in use...": "Имя маршрута было изменено, так как он уже используется...", + "The title for your share.": "Название для вашего сохранения.", + "There's no description :-(. To add one you'll need to login to OSM first, please use the button in the upper right corner to login.": "Нет описания 😞. Для добавления вам нужно сначала войти в систему OSM.", + "There's no permission to use your location. Would you like to open the app settings?": "Нет разрешения на использование вашего местоположения. Хотите открыть настройки приложения?", + "There's so much more you can do with our app": "С помощью нашего приложения вы можете сделать ещё многое", + "This map was generated from {{link}}Open Street Map (OSM){{linkend}} data which is free for all to use and edit.": "Эта карта была сгенерирована из данных {{link}}Open Street Map (OSM){{linkend}}, которые доступны всем для использования и редактирования.", + "This name is already in use": "Это имя уже используется", + "This will delete all current routes. Are you sure?": "Это удалит все текущие маршруты. Вы уверены?", + "Title": "Заголовок", + "Toilettes": "Туалеты", + "Traces are only saved locally. You can change that in the configuration settings": "Следы сохраняются только локально. Вы можете изменить это в настройках конфигурации.", + "Trails": "Тропы", + "Transportation": "Транспорт", + "Traveled distance": "Пройденное расстояние", + "Tree": "Дерево", + "Trunk": "Ствол", + "Tunnel": "Туннель", + "Type to search...": "Начните вводить для поиска...", + "Unable to delete the share...": "Не удалось удалить сохранение...", + "Unable to extract geographic information from the file...": "Не удалось извлечь географическую информацию из файла...", + "Unable to find the required point of interest...": "Не удалось найти нужную точку интереса...", + "Unable to find your location...": "Не удалось найти ваше местоположение...", + "Unable to generate URL, please try again later...": "Не удалось сгенерировать URL, пожалуйста, попробуйте снова позже...", + "Unable to get search results...": "Не удалось получить результаты поиска...", + "Unable to load from URL...": "Не удалось загрузить с URL...", + "Unable to login...": "Не удалось войти...", + "Unable to save an empty route, Please try and select a different one from the layers control on your left.": "Не удалось сохранить пустой маршрут, пожалуйста, попробуйте выбрать другой из бокового меню.", + "Unable to save data, please try again later...": "Не удалось сохранить данные, пожалуйста, попробуйте позже...", + "Unable to save to file...": "Не удалось сохранить в файл...", + "Unable to send route...": "Не удалось отправить маршрут...", + "Unable to upload the file...": "Не удалось загрузить файл...", + "Unarked Trail": "Неотмеченная тропа", + "Unclassified": "Неклассифицированный", + "Undo": "Отменить", + "Unhide hidden routes": "Показать скрытые маршруты", + "Unknown Scale": "Неизвестный масштаб", + "Up to zoom": "До увеличения", + "Update": "Обновить", + "Update current share": "Обновить текущее сохранение", + "Update the point's location": "Обновить местоположение точки", + "Upload a trace": "Загрузить след", + "Upload Point": "Загрузить точку в OpenStreetMap", + "Upload to Cloud and Share": "Сохранить в облаке и поделиться", + "Use the cloud icon to go offline": "Используйте иконку облака для переключения в офлайн", + "View": "Просмотр", + "Viewpoint": "Точка обзора", + "Vineyard": "Виноградник", + "Wadi": "Вади", + "Wall": "Стена", + "Water": "Вода", + "Water Tower or Water Tank": "Водонапорная башня или резервуар", + "Water Well": "Колодец", + "Waterfall": "Водопад", + "Waterhole": "Водопой", + "Website": "Сайт", + "Width": "Ширина", + "Wikimedia terms of service": "Условия использования Wikimedia", + "Wikipedia": "Википедия", + "Woods": "Лес", + "Would you like to update the point without the title?": "Хотите обновить точку без заголовка?", + "Would you like to update:": "Хотите обновить:", + "Wrapping things up, please wait a few seconds...": "Завершение... Пожалуйста, подождите несколько секунд...", + "Yes": "Да", + "You are about to download large files, you can change to wifi before clicking continue...": "Вы собираетесь загрузить большие файлы, вы можете переключиться на WiFi перед нажатием продолжить...", + "You can't edit while offline...": "Вы не можете редактировать в офлайне...", + "You need to login in order to see your traces, click the frowning face at the top": "Вам нужно войти в систему, чтобы увидеть свои следы, нажмите на лицо, которое хмурится, вверху", + "You need to login to OSM first, please use the button in the upper right corner to login.": "Вам нужно сначала войти в OSM, пожалуйста, используйте кнопку в верхнем правом углу для входа.", + "You should add your description here! Click the edit button above.": "Вы должны добавить здесь ваше описание! Нажмите кнопку редактирования выше.", + "Zoom In": "Увеличить", + "Zoom Out": "Уменьшить" +} \ No newline at end of file diff --git a/Tests/IsraelHiking.API.Tests/Controllers/OsmTracesControllerTests.cs b/Tests/IsraelHiking.API.Tests/Controllers/OsmTracesControllerTests.cs index ea513c9f..3d493b67 100644 --- a/Tests/IsraelHiking.API.Tests/Controllers/OsmTracesControllerTests.cs +++ b/Tests/IsraelHiking.API.Tests/Controllers/OsmTracesControllerTests.cs @@ -135,15 +135,15 @@ public void PostUploadRouteData_NotEnoughPoint_ShouldReturnBadRequest() } [TestMethod] - public void PostUploadRouteData_DefaultName_ShouldCreateTraceAndUpdateDescriptionToMatchArea() + public void PostUploadRouteData_Name_ShouldCreateTraceAndUpdateDescriptionToMatchArea() { _controller.SetupIdentity(); var osmGateWay = SetupOAuthClient(); var routeData = new RouteData { Id = "42", - Name = "Route", - Description = "Route", + Name = "Name", + Description = "Description", Segments = [ new RouteSegmentData @@ -162,7 +162,7 @@ public void PostUploadRouteData_DefaultName_ShouldCreateTraceAndUpdateDescriptio _controller.PostUploadRouteData(routeData, Languages.ENGLISH).Wait(); - osmGateWay.Received(1).CreateTrace(Arg.Is(f => f.Description.Contains("A route in")), Arg.Any()); + osmGateWay.Received(1).CreateTrace(Arg.Is(f => f.Description.Contains("Name")), Arg.Any()); } [TestMethod] @@ -193,7 +193,7 @@ public void PostUploadRouteData_RecordedUsingName_ShouldCreateTraceAndUpdateDesc _controller.PostUploadRouteData(routeData, Languages.ENGLISH).Wait(); - osmGateWay.Received(1).CreateTrace(Arg.Is(f => f.Description.Contains("A route in area") && f.Name.Contains("Recorded using IHM")), Arg.Any()); + osmGateWay.Received(1).CreateTrace(Arg.Is(f => f.Description.Contains("area") && f.Name.Contains("Recorded using IHM")), Arg.Any()); } [TestMethod] diff --git a/Tests/IsraelHiking.API.Tests/Controllers/PointsOfInterestControllerTests.cs b/Tests/IsraelHiking.API.Tests/Controllers/PointsOfInterestControllerTests.cs index 06bffeb4..874b9be5 100644 --- a/Tests/IsraelHiking.API.Tests/Controllers/PointsOfInterestControllerTests.cs +++ b/Tests/IsraelHiking.API.Tests/Controllers/PointsOfInterestControllerTests.cs @@ -242,7 +242,7 @@ public void GetClosestPoint_ShouldGetTheClosesOsmPoint() { _pointsOfInterestProvider.GetClosestPoint(Arg.Any(), Arg.Any(), Arg.Any()).Returns(new Feature(new Point(0,0), new AttributesTable())); - var results = _controller.GetClosestPoint("0,0", Sources.OSM, "he").Result; + var results = _controller.GetClosestPoint("0,0", Sources.OSM, Languages.HEBREW).Result; Assert.IsNotNull(results); } @@ -252,7 +252,7 @@ public void GetClosestPoint_NoSource_ShouldGetTheClosesPoint() { _pointsOfInterestProvider.GetClosestPoint(Arg.Any(), Arg.Any(), Arg.Any()).Returns(new Feature(new Point(0,0), new AttributesTable())); - var results = _controller.GetClosestPoint("0,0", null, "he").Result; + var results = _controller.GetClosestPoint("0,0", null, Languages.HEBREW).Result; Assert.IsNotNull(results); } diff --git a/Tests/IsraelHiking.API.Tests/Executors/FeaturesMergeExecutorTests.cs b/Tests/IsraelHiking.API.Tests/Executors/FeaturesMergeExecutorTests.cs index b518e725..fb4b853e 100644 --- a/Tests/IsraelHiking.API.Tests/Executors/FeaturesMergeExecutorTests.cs +++ b/Tests/IsraelHiking.API.Tests/Executors/FeaturesMergeExecutorTests.cs @@ -52,13 +52,13 @@ public void MergeFeatures_HasSameTitleOSMSource_ShouldMergeWithoutLink() { var feature1 = CreateFeature("1", 0, 0); feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME, "1"); - feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":he", "11"); + feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.HEBREW, "11"); feature1.Attributes.AddOrUpdate(FeatureAttributes.WEBSITE, "website"); feature1.SetTitles(); var feature2 = CreateFeature("2", 0, 0); feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME, "1"); - feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":en", "11"); - feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":en", "11"); + feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.ENGLISH, "11"); + feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.ENGLISH, "11"); feature2.SetTitles(); var results = _executor.Merge([feature1, feature2], []); @@ -125,7 +125,7 @@ public void MergeFeatures_HasSameTitleDifferentSource_ShouldMergeWithLink() { var feature1 = CreateFeature("1", 0, 0); feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME, "1"); - feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":he", "11"); + feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.HEBREW, "11"); feature1.Attributes.AddOrUpdate(FeatureAttributes.POI_CATEGORY, Categories.NONE); feature1.Attributes.AddOrUpdate(FeatureAttributes.POI_SEARCH_FACTOR, 0.5); feature1.Attributes.AddOrUpdate(FeatureAttributes.POI_ICON, string.Empty); @@ -156,11 +156,11 @@ public void MergeFeatures_HasSameTitleButFarAway_ShouldNotMerge() { var feature1 = CreateFeature("1", 0, 0); feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME, "1"); - feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":he", "11"); + feature1.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.HEBREW, "11"); feature1.SetTitles(); var feature2 = CreateFeature("2", 0, 0.5); feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME, "1"); - feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":en", "11"); + feature2.Attributes.AddOrUpdate(FeatureAttributes.NAME + ":" + Languages.ENGLISH, "11"); feature2.SetTitles(); var results = _executor.Merge([feature1, feature2], []); diff --git a/Tests/IsraelHiking.API.Tests/Services/Middleware/CrawlersMiddlewareTests.cs b/Tests/IsraelHiking.API.Tests/Services/Middleware/CrawlersMiddlewareTests.cs index 46da6241..3a361f48 100644 --- a/Tests/IsraelHiking.API.Tests/Services/Middleware/CrawlersMiddlewareTests.cs +++ b/Tests/IsraelHiking.API.Tests/Services/Middleware/CrawlersMiddlewareTests.cs @@ -193,7 +193,7 @@ public void TestCrawler_PoiWithExternalDescription_ShouldReturnExternal() _middleware.InvokeAsync(context, detectionService).Wait(); - _homePageHelper.Received().Render(name, externalDescription, Arg.Any(), "he"); + _homePageHelper.Received().Render(name, externalDescription, Arg.Any(), Languages.HEBREW); _next.DidNotReceive().Invoke(context); } diff --git a/Tests/IsraelHiking.API.Tests/Services/Poi/PointsOfInterestProviderTests.cs b/Tests/IsraelHiking.API.Tests/Services/Poi/PointsOfInterestProviderTests.cs index 6283bbae..debc1c0a 100644 --- a/Tests/IsraelHiking.API.Tests/Services/Poi/PointsOfInterestProviderTests.cs +++ b/Tests/IsraelHiking.API.Tests/Services/Poi/PointsOfInterestProviderTests.cs @@ -90,21 +90,21 @@ public void GetFeatureById_RouteWithMultipleAttributes_ShouldReturnIt() node.Tags.Add(new Tag(FeatureAttributes.IMAGE_URL, FeatureAttributes.IMAGE_URL)); node.Tags.Add(new Tag(FeatureAttributes.IMAGE_URL+ "1", FeatureAttributes.IMAGE_URL+ "1")); node.Tags.Add(new Tag(FeatureAttributes.DESCRIPTION, FeatureAttributes.DESCRIPTION)); - node.Tags.Add(new Tag(FeatureAttributes.WIKIPEDIA + ":en", "page with space")); + node.Tags.Add(new Tag(FeatureAttributes.WIKIPEDIA + ":" + Languages.ENGLISH, "page with space")); gateway.GetNode(node.Id.Value).Returns(node); var result = _adapter.GetFeatureById(Sources.OSM, someId).Result; Assert.IsNotNull(result); - Assert.AreEqual(string.Empty, result.GetTitle("en")); - Assert.AreEqual(FeatureAttributes.DESCRIPTION, result.GetDescription("en")); + Assert.AreEqual(string.Empty, result.GetTitle(Languages.ENGLISH)); + Assert.AreEqual(FeatureAttributes.DESCRIPTION, result.GetDescription(Languages.ENGLISH)); var imagesUrls = result.Attributes.GetNames() .Where(n => n.StartsWith(FeatureAttributes.IMAGE_URL)) .Select(p => node.Tags.GetValue(p).ToString()) .ToArray(); Assert.AreEqual(2, imagesUrls.Length); Assert.AreEqual(FeatureAttributes.IMAGE_URL, imagesUrls.First()); - Assert.IsTrue(result.Attributes[FeatureAttributes.WIKIPEDIA + ":en"].ToString()?.Contains("page with space")); + Assert.IsTrue(result.Attributes[FeatureAttributes.WIKIPEDIA + ":" + Languages.ENGLISH].ToString()?.Contains("page with space")); } [TestMethod] @@ -156,7 +156,7 @@ public void AddFeature_ShouldUpdateOsmAndElasticSearch() var user = new User { DisplayName = "DisplayName" }; var gateway = SetupOsmAuthClient(); gateway.GetUserDetails().Returns(user); - var language = "he"; + var language = Languages.HEBREW; gateway.CreateElement(Arg.Any(), Arg.Any()).Returns(42); var feature = GetValidFeature("42", Sources.OSM); feature.Attributes.AddOrUpdate(FeatureAttributes.IMAGE_URL, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//" + @@ -181,7 +181,7 @@ public void AddFeature_WithExtraSpaces_ShouldRemoveExtraSpaces() var user = new User { DisplayName = "DisplayName" }; var gateway = SetupOsmAuthClient(); gateway.GetUserDetails().Returns(user); - var language = "he"; + var language = Languages.HEBREW; gateway.CreateElement(Arg.Any(), Arg.Any()).Returns(42); var feature = GetValidFeature("42", Sources.OSM); feature.Attributes.AddOrUpdate(FeatureAttributes.POI_ICON, _tagsHelper.GetCategoriesByGroup(Categories.POINTS_OF_INTEREST).First().Icon); @@ -203,7 +203,7 @@ public void AddFeature_WithExtraSpaces_ShouldRemoveExtraSpaces() public void AddFeature_WikipediaMobileLink_ShouldUpdateOsmAndElasticSearch() { var gateway = SetupOsmAuthClient(); - var language = "he"; + var language = Languages.HEBREW; gateway.CreateElement(Arg.Any(), Arg.Any()).Returns(42); var feature = GetValidFeature("42", Sources.OSM); feature.Attributes.AddOrUpdate(FeatureAttributes.POI_ICON, _tagsHelper.GetCategoriesByGroup(Categories.POINTS_OF_INTEREST).First().Icon); @@ -233,9 +233,9 @@ public void UpdateFeature_CreateWikipediaTag() Longitude = 0 }); - _adapter.UpdateFeature(feature, gateway, "en").Wait(); + _adapter.UpdateFeature(feature, gateway, Languages.ENGLISH).Wait(); - gateway.Received().UpdateElement(Arg.Any(), Arg.Is(x => x.Tags.ContainsKey(FeatureAttributes.WIKIPEDIA + ":en") && x.Tags.Contains(FeatureAttributes.WIKIPEDIA, "en:Literary Hall"))); + gateway.Received().UpdateElement(Arg.Any(), Arg.Is(x => x.Tags.ContainsKey(FeatureAttributes.WIKIPEDIA + ":" + Languages.ENGLISH) && x.Tags.Contains(FeatureAttributes.WIKIPEDIA, "en:Literary Hall"))); gateway.Received().CreateChangeset(Arg.Any()); gateway.Received().CloseChangeset(Arg.Any()); } @@ -252,18 +252,18 @@ public void UpdateFeature_HasLanguageSpecificDescription_ShouldUpdateBoth() Id = 1, Tags = new TagsCollection { - { FeatureAttributes.DESCRIPTION + ":en", "description" }, + { FeatureAttributes.DESCRIPTION + ":" + Languages.ENGLISH, "description" }, { FeatureAttributes.DESCRIPTION, "description" } }, Latitude = 0, Longitude = 0 }); - _adapter.UpdateFeature(feature, gateway, "en").Wait(); + _adapter.UpdateFeature(feature, gateway, Languages.ENGLISH).Wait(); gateway.Received().UpdateElement(Arg.Any(), Arg.Is(x => - x.Tags.GetValue(FeatureAttributes.DESCRIPTION + ":en") == "new description" && + x.Tags.GetValue(FeatureAttributes.DESCRIPTION + ":" + Languages.ENGLISH) == "new description" && x.Tags.GetValue(FeatureAttributes.DESCRIPTION) == "new description")); } @@ -279,13 +279,13 @@ public void UpdateFeature_UpdateLocationToALocationTooClose_ShouldNotUpdate() Tags = new TagsCollection { {FeatureAttributes.NAME, "name"}, - {FeatureAttributes.NAME + ":en", "name"} + {FeatureAttributes.NAME + ":" + Languages.ENGLISH, "name"} }, Latitude = 1, Longitude = 1 }); - _adapter.UpdateFeature(feature, gateway, "en").Wait(); + _adapter.UpdateFeature(feature, gateway, Languages.ENGLISH).Wait(); gateway.DidNotReceive().UpdateElement(Arg.Any(), Arg.Any()); } @@ -307,13 +307,13 @@ public void UpdateFeature_OnlyChangeExtraSpaces_ShouldNotUpdate() Tags = new TagsCollection { {FeatureAttributes.NAME, "name"}, - {FeatureAttributes.NAME + ":en", "name"} + {FeatureAttributes.NAME + ":" + Languages.ENGLISH, "name"} }, Latitude = 1, Longitude = 1 }); - _adapter.UpdateFeature(featureUpdate, gateway, "en").Wait(); + _adapter.UpdateFeature(featureUpdate, gateway, Languages.ENGLISH).Wait(); gateway.DidNotReceive().UpdateElement(Arg.Any(), Arg.Any()); } @@ -330,7 +330,7 @@ public void UpdateFeature_UpdateLocationOfWay_ShouldNotUpdate() Tags = new TagsCollection { { FeatureAttributes.NAME, "name" }, - { FeatureAttributes.NAME + ":en", "name" } + { FeatureAttributes.NAME + ":" + Languages.ENGLISH, "name" } }, Nodes = [ @@ -349,7 +349,7 @@ public void UpdateFeature_UpdateLocationOfWay_ShouldNotUpdate() ] }); - _adapter.UpdateFeature(feature, gateway, "en").Wait(); + _adapter.UpdateFeature(feature, gateway, Languages.ENGLISH).Wait(); gateway.DidNotReceive().UpdateElement(Arg.Any(), Arg.Any()); } diff --git a/Tests/IsraelHiking.DataAccess.Tests/ElasticSearch/ElasticSearchGatewayTests.cs b/Tests/IsraelHiking.DataAccess.Tests/ElasticSearch/ElasticSearchGatewayTests.cs index ca3d8ea9..183f06e5 100644 --- a/Tests/IsraelHiking.DataAccess.Tests/ElasticSearch/ElasticSearchGatewayTests.cs +++ b/Tests/IsraelHiking.DataAccess.Tests/ElasticSearch/ElasticSearchGatewayTests.cs @@ -32,15 +32,24 @@ public void TestInitialize() [Ignore] public void Search_ShouldReturnResults() { - var results = _gateway.Search("מנות", "name").Result; - Assert.AreEqual(10, results.Count); + var results = _gateway.Search("מנות", Languages.HEBREW).Result; + Assert.AreEqual(20, results.Count); + } + + [TestMethod] + [Ignore] + public void SearchRussian_ShouldReturnResults() + { + // Caesarea + var results = _gateway.Search("Кейсария", Languages.HEBREW).Result; + Assert.AreEqual(20, results.Count); } [TestMethod] [Ignore] public void GetContainerName_ShouldReturnResults() { - var results = _gateway.GetContainerName([new Coordinate(35.05746, 32.596838)], "he").Result; + var results = _gateway.GetContainerName([new Coordinate(35.05746, 32.596838)], Languages.HEBREW).Result; Assert.AreEqual("רמות מנשה", results); } @@ -78,7 +87,7 @@ public void GetClosestPoint_ShouldReturnResults() public void GetClosestPoint_NoSourceIsSpecified_ShouldReturnResults() { var coordinate = new Coordinate(35.23087, 32.93687); - var results = _gateway.GetClosestPoint(coordinate, null, "he").Result; + var results = _gateway.GetClosestPoint(coordinate, null, Languages.HEBREW).Result; Assert.IsNotNull(results); } @@ -87,7 +96,7 @@ public void GetClosestPoint_NoSourceIsSpecified_ShouldReturnResults() public void GetClosestPoint_OSMSourceSpecified_ShouldNotReturnResults() { var coordinate = new Coordinate(35.23087, 32.93687); - var results = _gateway.GetClosestPoint(coordinate, Sources.OSM, "he").Result; + var results = _gateway.GetClosestPoint(coordinate, Sources.OSM, Languages.HEBREW).Result; Assert.IsNull(results); }