diff --git a/data/strings/strings.txt b/data/strings/strings.txt index b8ffcc8911..aa000f0b69 100644 --- a/data/strings/strings.txt +++ b/data/strings/strings.txt @@ -5888,7 +5888,7 @@ [pref_tts_test_voice_title] comment = Settings «Route» category: «Test Voice Directions» title - tags = android + tags = android,ios en = Test Voice Directions (TTS, Text-To-Speech) ar = اختبار الاتجاهات الصوتية (TTS، تحويل النص إلى كلام) az = Test səsli göstərişlər (TTS, Text-to-Speech) @@ -28771,7 +28771,7 @@ [app_tip_00] comment = App Tip #00 - tags = android + tags = android,ios en = Thank you for using our community-built maps! ar = شكرًا لك على استخدام الخرائط التي أنشأها مجتمعنا! az = İcma tərəfindən yaradılmış xəritələrimizdən istifadə etdiyiniz üçün təşəkkür edirik! @@ -28815,7 +28815,7 @@ [app_tip_01] comment = App tip #01 - tags = android + tags = android,ios en = With your donations and support, we can create the best maps in the World! ar = بتبرعاتكم ودعمكم، يمكننا إنشاء أفضل الخرائط في العالم! be = Дзякуючы вашым ахвяраванням і падтрымцы мы можам стварыць лепшыя мапы у свеце! @@ -28858,7 +28858,7 @@ [app_tip_02] comment = App tip #02 - tags = android + tags = android,ios en = Do you like our app? Please donate to support the development! Don't like it yet? Please let us know why, and we will fix it! ar = هل تحب التطبيق لدينا؟ يرجى التبرع لدعم التنمية! لا أحب ذلك حتى الآن؟ واسمحوا لنا أن نعرف، وسوف نقوم بإصلاحه! be = Вам падабаецца наша прылада? Калі ласка, ахвяруйце, каб падтрымаць развіццё! Яшчэ не падабаецца? Калі ласка, напішыце, чаму, і мы гэта выправім! @@ -28901,7 +28901,7 @@ [app_tip_03] comment = App tip #03 - tags = android + tags = android,ios en = If you know a software developer, you can ask him or her to implement a feature that you need. ar = إذا كنت تعرف أحد مطوري البرامج، فيمكنك أن تطلب منه تنفيذ الميزة التي تحتاجها. be = Калі вы ведаеце распрацоўшчыка праграмнага забеспячэння, вы можаце папрасіць яго рэалізаваць неабходную вам функцыю. @@ -28944,7 +28944,7 @@ [app_tip_04] comment = App tip #04 - tags = android + tags = android,ios en = Do you know that you can long-tap any place on the map to select it? ar = هل تعلم أنه يمكنك النقر لفترة طويلة على أي مكان على الخريطة لتحديده؟ be = Любое месца на карце можна абраць, калі патрымаць там палец на працягу секунды. @@ -28987,7 +28987,7 @@ [app_tip_05] comment = App tip #05 - tags = android + tags = android,ios en = Did you know that your current location on the map can be selected? ar = هل تعلم أنه يمكنك تحديد موقعك الحالي على الخريطة؟ be = А вы ведалі, што сваё месцазнаходжанне на мапе можна абраць? @@ -29030,7 +29030,7 @@ [app_tip_06] comment = App tip #06 - tags = android + tags = android,ios en = You can help to translate our app into your language. ar = يمكنك المساعدة في ترجمة تطبيقنا إلى لغتك. be = Вы можаце дапамагчы перакласці нашу прыладу на вашу мову. @@ -29073,7 +29073,7 @@ [app_tip_07] comment = App tip #07 - tags = android + tags = android,ios en = Our app is developed by a few enthusiasts and the community. ar = تم تطوير تطبيقنا من قبل عدد قليل من المتحمسين والمجتمع. be = Наша прылада распрацавана некалькімі энтузіястамі і супольнасцю. @@ -29116,7 +29116,7 @@ [app_tip_08] comment = App tip #08 - tags = android + tags = android,ios en = You can easily fix and improve the map data. ar = يمكنك بسهولة إصلاح وتحسين بيانات الخريطة. be = Вы можаце лёгка выправіць і палепшыць дадзеныя карты. @@ -29159,7 +29159,7 @@ [app_tip_09] comment = App tip #09 - tags = android + tags = android,ios en = Our main goal is to build fast, privacy-focused, easy-to-use maps that you will love. ar = هدفنا الرئيسي هو إنشاء خرائط سريعة وسهلة الاستخدام تركز على الخصوصية والتي ستعجبك. be = Наша галоўная мэта - ствараць хуткія, арыентаваныя на прыватнасць і простыя ў выкарыстанні карты, якія вам спадабаюцца. diff --git a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h index 62c85b251e..7e5d030a33 100644 --- a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h +++ b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.h @@ -18,6 +18,7 @@ - (void)setNotificationsLocale:(NSString *)locale; - (void)playTurnNotifications:(NSArray *)turnNotifications; - (void)playWarningSound; +- (void)play:(NSString *)text; - (instancetype)init __attribute__((unavailable("call +tts instead"))); - (instancetype)copy __attribute__((unavailable("call +tts instead"))); diff --git a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm index 20dd5703b9..c7f7658c54 100644 --- a/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm +++ b/iphone/Maps/Core/TextToSpeech/MWMTextToSpeech.mm @@ -279,6 +279,13 @@ using Observers = NSHashTable; return _audioPlayer; } +- (void)play:(NSString *)text { + if (![self isValid]) + [self createVoice:[[self class] savedLanguage]]; + + [self speakOneString:text]; +} + #pragma mark - MWMNavigationDashboardObserver - (void)onTTSStatusUpdated { diff --git a/iphone/Maps/Core/TextToSpeech/TTSTester.h b/iphone/Maps/Core/TextToSpeech/TTSTester.h new file mode 100644 index 0000000000..f737dc3ddf --- /dev/null +++ b/iphone/Maps/Core/TextToSpeech/TTSTester.h @@ -0,0 +1,10 @@ +NS_ASSUME_NONNULL_BEGIN + +@interface TTSTester : NSObject + +- (void)playRandomTestString; +- (NSArray *)getTestStrings:(NSString *)language; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iphone/Maps/Core/TextToSpeech/TTSTester.mm b/iphone/Maps/Core/TextToSpeech/TTSTester.mm new file mode 100644 index 0000000000..01b03c50c8 --- /dev/null +++ b/iphone/Maps/Core/TextToSpeech/TTSTester.mm @@ -0,0 +1,58 @@ +#import "TTSTester.h" + +#include +#include "LocaleTranslator.h" +#include "MWMTextToSpeech.h" + +@implementation TTSTester + +static NSString * const NotFoundDelimiter = @"__not_found__"; + +NSArray * testStrings; +NSString * testStringsLanguage; + +int testStringIndex; + +- (void)playRandomTestString { + NSString * currentTTSLanguage = MWMTextToSpeech.savedLanguage; + if (testStrings == nil || ![currentTTSLanguage isEqualToString:testStringsLanguage]) { + testStrings = [self getTestStrings:currentTTSLanguage]; + if (testStrings == nil) { + LOG(LWARNING, ("Couldn't load TTS test strings")); + return; + } + testStringsLanguage = currentTTSLanguage; + } + + [[MWMTextToSpeech tts] play:testStrings[testStringIndex]]; + + if (++testStringIndex >= testStrings.count) + testStringIndex = 0; +} + +- (NSArray *)getTestStrings:(NSString *)language { + NSString * twineLanguage = [NSString stringWithUTF8String:locale_translator::bcp47ToTwineLanguage(language).c_str()]; + NSString * languagePath = [NSBundle.mainBundle pathForResource:twineLanguage ofType:@"lproj"]; + if (languagePath == nil) { + LOG(LWARNING, ("Couldn't find translation file for ", twineLanguage.UTF8String)); + return nil; + } + NSBundle * bundle = [NSBundle bundleWithPath:languagePath]; + + NSMutableArray * appTips = [NSMutableArray new]; + for (int idx = 0; ; idx++) { + NSString * appTipKey = [NSString stringWithFormat:@"app_tip_%02d", idx]; + NSString * appTip = [bundle localizedStringForKey:appTipKey value:NotFoundDelimiter table:nil]; + if ([appTip isEqualToString:NotFoundDelimiter]) + break; + [appTips addObject:appTip]; + } + + // shuffle + for (NSUInteger i = appTips.count; i > 1; i--) + [appTips exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)]; + + return appTips; +} + +@end diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index fb473654b1..f356e847f3 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -261,6 +261,8 @@ 47F86D0120C93D8D00FEE291 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F86D0020C93D8D00FEE291 /* TabViewController.swift */; }; 4A300ED51C6DCFD400140018 /* countries-strings in Resources */ = {isa = PBXBuildFile; fileRef = 4A300ED31C6DCFD400140018 /* countries-strings */; }; 4B4153B52BF9695500EE4B02 /* MWMTextToSpeechTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B4153B42BF9695500EE4B02 /* MWMTextToSpeechTests.mm */; }; + 4B83AE492C2E59F800B0C3BC /* TTSTester.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B83AE482C2E59F800B0C3BC /* TTSTester.mm */; }; + 4B83AE4B2C2E642100B0C3BC /* TTSTesterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B83AE4A2C2E642100B0C3BC /* TTSTesterTest.m */; }; 6741A9421BF340DE002C974C /* sound-strings in Resources */ = {isa = PBXBuildFile; fileRef = 5605022E1B6211E100169CAD /* sound-strings */; }; 6741A9451BF340DE002C974C /* classificator.txt in Resources */ = {isa = PBXBuildFile; fileRef = EE026F0511D6AC0D00645242 /* classificator.txt */; }; 6741A9491BF340DE002C974C /* countries.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA46DA2B12D4166E00968C36 /* countries.txt */; }; @@ -1186,6 +1188,9 @@ 4A7D89C31B2EBF3B00AC843E /* resources-xhdpi_dark */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-xhdpi_dark"; path = "../../data/resources-xhdpi_dark"; sourceTree = ""; }; 4A7D89C41B2EBF3B00AC843E /* resources-xxhdpi_dark */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-xxhdpi_dark"; path = "../../data/resources-xxhdpi_dark"; sourceTree = ""; }; 4B4153B42BF9695500EE4B02 /* MWMTextToSpeechTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MWMTextToSpeechTests.mm; sourceTree = ""; }; + 4B83AE472C2E59F800B0C3BC /* TTSTester.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TTSTester.h; sourceTree = ""; }; + 4B83AE482C2E59F800B0C3BC /* TTSTester.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TTSTester.mm; sourceTree = ""; }; + 4B83AE4A2C2E642100B0C3BC /* TTSTesterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TTSTesterTest.m; sourceTree = ""; }; 5605022E1B6211E100169CAD /* sound-strings */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "sound-strings"; path = "../../data/sound-strings"; sourceTree = ""; }; 6741AA5D1BF340DE002C974C /* Organic Maps (Debug).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Organic Maps (Debug).app"; sourceTree = BUILT_PRODUCTS_DIR; }; 6B15907026623AE500944BBA /* 00_NotoSansThai-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "00_NotoSansThai-Regular.ttf"; path = "../../data/00_NotoSansThai-Regular.ttf"; sourceTree = ""; }; @@ -2289,6 +2294,8 @@ 34763EE51F2F392300F4D2D3 /* MWMTextToSpeech.mm */, 34763EE91F2F394D00F4D2D3 /* MWMTextToSpeech+CPP.h */, 34763EEA1F2F3AD700F4D2D3 /* MWMTextToSpeechObserver.h */, + 4B83AE472C2E59F800B0C3BC /* TTSTester.h */, + 4B83AE482C2E59F800B0C3BC /* TTSTester.mm */, ); path = TextToSpeech; sourceTree = ""; @@ -2666,6 +2673,7 @@ 4B4153B72BF970A000EE4B02 /* TextToSpeech */ = { isa = PBXGroup; children = ( + 4B83AE4A2C2E642100B0C3BC /* TTSTesterTest.m */, 4B4153B42BF9695500EE4B02 /* MWMTextToSpeechTests.mm */, ); path = TextToSpeech; @@ -4477,6 +4485,7 @@ 3404755F1E081A4600C92850 /* MWMLocationPredictor.mm in Sources */, 993DF11523F6BDB100AC231A /* UISearchBarRenderer.swift in Sources */, F6E2FE041E097BA00083EBEC /* MWMOpeningHoursDaysSelectorTableViewCell.mm in Sources */, + 4B83AE492C2E59F800B0C3BC /* TTSTester.mm in Sources */, 993DF12023F6BDB100AC231A /* TabViewRenderer.swift in Sources */, F6E2FE131E097BA00083EBEC /* MWMOpeningHoursTimeSelectorTableViewCell.mm in Sources */, F626D52F1C3E83F800C17D15 /* MWMTableViewCell.m in Sources */, @@ -4629,6 +4638,7 @@ EDF838C32C00B9D6007E4E67 /* UbiquitousDirectoryMonitorDelegateMock.swift in Sources */, EDF838BE2C00B9D0007E4E67 /* LocalDirectoryMonitorDelegateMock.swift in Sources */, EDF838BF2C00B9D0007E4E67 /* SynchronizationStateManagerTests.swift in Sources */, + 4B83AE4B2C2E642100B0C3BC /* TTSTesterTest.m in Sources */, EDF838C22C00B9D6007E4E67 /* MetadataItemStubs.swift in Sources */, 4B4153B52BF9695500EE4B02 /* MWMTextToSpeechTests.mm in Sources */, EDF838C42C00B9D6007E4E67 /* FileManagerMock.swift in Sources */, diff --git a/iphone/Maps/Tests/Core/TextToSpeech/TTSTesterTest.m b/iphone/Maps/Tests/Core/TextToSpeech/TTSTesterTest.m new file mode 100644 index 0000000000..addb114d46 --- /dev/null +++ b/iphone/Maps/Tests/Core/TextToSpeech/TTSTesterTest.m @@ -0,0 +1,28 @@ +#import +#import "TTSTester.h" + +@interface TTSTesterTest : XCTestCase + +@end + +@implementation TTSTesterTest + +TTSTester * ttsTester; + +- (void)setUp { + ttsTester = [[TTSTester alloc] init]; +} + +- (void)testTestStringsWithEnglish { + XCTAssertTrue([[ttsTester getTestStrings:@"en-US"] containsObject: @"Thank you for using our community-built maps!"]); +} + +- (void)testTestStringsWithGerman { + XCTAssertTrue([[ttsTester getTestStrings:@"de-DE"] containsObject: @"Danke, dass du unsere von der Community erstellten Karten benutzt!"]); +} + +- (void)testTestStringsWithInvalidLanguage { + XCTAssertNil([ttsTester getTestStrings:@"xxx"]); +} + +@end diff --git a/iphone/Maps/UI/Settings/MWMTTSSettingsViewController.mm b/iphone/Maps/UI/Settings/MWMTTSSettingsViewController.mm index 963be3f5d0..cb9a2783c5 100644 --- a/iphone/Maps/UI/Settings/MWMTTSSettingsViewController.mm +++ b/iphone/Maps/UI/Settings/MWMTTSSettingsViewController.mm @@ -1,6 +1,7 @@ #import "MWMTTSSettingsViewController.h" #import #import "MWMTextToSpeech+CPP.h" +#import "TTSTester.h" #import "SwiftBridge.h" #include @@ -63,10 +64,13 @@ struct VoiceInstructionCellStrategy : BaseCellStategy struct LanguageCellStrategy : BaseCellStategy { + TTSTester * ttsTester = [[TTSTester alloc] init]; + UITableViewCell * BuildCell(UITableView * tableView, NSIndexPath * indexPath, MWMTTSSettingsViewController * controller) override { NSInteger const row = indexPath.row; + // "Other" cell if (row == controller.languages.size()) { Class cls = [SettingsTableViewLinkCell class]; @@ -75,6 +79,17 @@ struct LanguageCellStrategy : BaseCellStategy [cell configWithTitle:L(@"pref_tts_other_section_title") info:nil]; return cell; } + + // "Test TTS" cell + if (row == controller.languages.size() + 1) + { + Class cls = [SettingsTableViewSelectableCell class]; + auto cell = static_cast( + [tableView dequeueReusableCellWithCellClass:cls indexPath:indexPath]); + [cell configWithTitle:L(@"pref_tts_test_voice_title")]; + cell.accessoryType = UITableViewCellAccessoryNone; + return cell; + } Class cls = [SettingsTableViewSelectableCell class]; auto cell = static_cast( @@ -97,7 +112,7 @@ struct LanguageCellStrategy : BaseCellStategy size_t NumberOfRows(MWMTTSSettingsViewController * controller) const override { - return controller.languages.size() + 1; // Number of languages + "Other" cell + return controller.languages.size() + 2; // Number of languages + "Other" cell + "TTS Test" cell } NSString * TitleForHeader() const override { return L(@"pref_tts_language_title"); } @@ -112,6 +127,12 @@ struct LanguageCellStrategy : BaseCellStategy return; } + if (row == controller.languages.size() + 1) + { + [ttsTester playRandomTestString]; + return; + } + auto cell = [tableView cellForRowAtIndexPath:indexPath]; if (m_selectedCell == cell) return;