This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended. To summarise this work: - [x] created a new **Xcode** project; - [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project; - [x] created the `Locations` app and also, its `Libraries` package; - [x] created the `Shared` package to share dependencies between the apps; - [x] added a `Makefile` file and implemented some **environment** and **help** commands. Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Reviewed-on: rock-n-code/deep-linking-assignment#1
409 lines
22 KiB
Objective-C
409 lines
22 KiB
Objective-C
#import <XCTest/XCTest.h>
|
|
|
|
@import WMF;
|
|
|
|
@interface TWNStringsTests : XCTestCase
|
|
|
|
@property (class, strong, nonatomic, readonly) NSArray *bundledLprojFiles;
|
|
@property (class, strong, nonatomic, readonly) NSArray *iOSLprojFiles;
|
|
@property (class, strong, nonatomic, readonly) NSArray *twnLprojFiles;
|
|
@property (class, strong, nonatomic, readonly) NSString *bundleRoot;
|
|
@property (class, strong, nonatomic, readonly) NSArray *twnInfoPlistFilePaths;
|
|
@property (class, strong, nonatomic, readonly) NSArray *iOSInfoPlistFilePaths;
|
|
@property (class, strong, nonatomic, readonly) NSString *twnLocalizationsDirectory;
|
|
@property (class, strong, nonatomic, readonly) NSString *iOSLocalizationsDirectory;
|
|
|
|
@end
|
|
|
|
@implementation TWNStringsTests
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
}
|
|
|
|
+ (NSString *)iOSLocalizationsDirectory {
|
|
return [SOURCE_ROOT_DIR stringByAppendingPathComponent:@"Wikipedia/iOS Native Localizations"];
|
|
}
|
|
|
|
+ (NSString *)twnLocalizationsDirectory {
|
|
return [SOURCE_ROOT_DIR stringByAppendingPathComponent:@"Wikipedia/Localizations"];
|
|
}
|
|
|
|
+ (NSString *)bundleRoot {
|
|
return [[NSBundle wmf_localizationBundle] bundlePath];
|
|
}
|
|
|
|
+ (NSString *)appBundleRoot {
|
|
return [[NSBundle mainBundle] bundlePath];
|
|
}
|
|
|
|
+ (NSArray *)bundledLprojFiles {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *bundledLprojFiles;
|
|
dispatch_once(&onceToken, ^{
|
|
bundledLprojFiles = [[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:TWNStringsTests.bundleRoot error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension='lproj'"]] valueForKey:@"lowercaseString"];
|
|
});
|
|
return bundledLprojFiles;
|
|
}
|
|
|
|
+ (NSArray *)twnInfoPlistFilePaths {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *twnInfoPlistFilePaths;
|
|
dispatch_once(&onceToken, ^{
|
|
twnInfoPlistFilePaths = [self.twnLprojFiles wmf_map:^NSString *(NSString *lprojFileName) {
|
|
return [[self.twnLocalizationsDirectory stringByAppendingPathComponent:lprojFileName] stringByAppendingPathComponent:@"InfoPlist.strings"];
|
|
}];
|
|
});
|
|
return twnInfoPlistFilePaths;
|
|
}
|
|
|
|
+ (NSArray *)iOSInfoPlistFilePaths {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *infoPlistFilePaths;
|
|
dispatch_once(&onceToken, ^{
|
|
infoPlistFilePaths = [self.iOSLprojFiles wmf_map:^NSString *(NSString *lprojFileName) {
|
|
return [[self.iOSLocalizationsDirectory stringByAppendingPathComponent:lprojFileName] stringByAppendingPathComponent:@"InfoPlist.strings"];
|
|
}];
|
|
});
|
|
return infoPlistFilePaths;
|
|
}
|
|
|
|
+ (NSArray *)twnLprojFiles {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *twnLprojFiles;
|
|
dispatch_once(&onceToken, ^{
|
|
twnLprojFiles = [[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.twnLocalizationsDirectory error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension='lproj'"]] valueForKey:@"lowercaseString"];
|
|
});
|
|
return twnLprojFiles;
|
|
}
|
|
|
|
+ (NSArray *)iOSLprojFiles {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *iOSLprojFiles;
|
|
dispatch_once(&onceToken, ^{
|
|
iOSLprojFiles = [[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.iOSLocalizationsDirectory error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension='lproj'"]] valueForKey:@"lowercaseString"];
|
|
});
|
|
return iOSLprojFiles;
|
|
}
|
|
|
|
- (NSDictionary *)getPluralizableStringsDictFromLprogAtPath:(NSString *)lprojPath {
|
|
NSString *stringsFilePath = [lprojPath stringByAppendingPathComponent:@"Localizable.stringsdict"];
|
|
return [self getDictFromPListAtPath:stringsFilePath];
|
|
}
|
|
|
|
- (NSDictionary *)getTranslationStringsDictFromLprogAtPath:(NSString *)lprojPath {
|
|
NSString *stringsFilePath = [lprojPath stringByAppendingPathComponent:@"Localizable.strings"];
|
|
return [self getDictFromPListAtPath:stringsFilePath];
|
|
}
|
|
|
|
- (NSDictionary *)getDictFromPListAtPath:(NSString *)path {
|
|
BOOL isDirectory = NO;
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
|
|
return [NSDictionary dictionaryWithContentsOfFile:path];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)testLprojCount {
|
|
XCTAssert(TWNStringsTests.iOSLprojFiles.count > 0);
|
|
XCTAssert(TWNStringsTests.twnLprojFiles.count > 0);
|
|
}
|
|
|
|
+ (NSRegularExpression *)reverseiOSTokenRegex {
|
|
static dispatch_once_t onceToken;
|
|
static NSRegularExpression *reverseiOSTokenRegex;
|
|
dispatch_once(&onceToken, ^{
|
|
reverseiOSTokenRegex = [NSRegularExpression regularExpressionWithPattern:@"(:?[^%%])(:?[0-9]+)(?:[$])(:?[^@dDuUxXoOfeEgGcCsSpaAF])" options:0 error:nil];
|
|
});
|
|
return reverseiOSTokenRegex;
|
|
}
|
|
|
|
+ (NSRegularExpression *)reverseTWNTokenRegex {
|
|
static dispatch_once_t onceToken;
|
|
static NSRegularExpression *reverseTWNTokenRegex;
|
|
dispatch_once(&onceToken, ^{
|
|
reverseTWNTokenRegex = [NSRegularExpression regularExpressionWithPattern:@"(:?[0-9])(?:[$])(:?[^0-9])" options:0 error:nil];
|
|
});
|
|
return reverseTWNTokenRegex;
|
|
}
|
|
|
|
+ (NSRegularExpression *)twnTokenRegex {
|
|
static dispatch_once_t onceToken;
|
|
static NSRegularExpression *twnTokenRegex;
|
|
dispatch_once(&onceToken, ^{
|
|
twnTokenRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:[$])(:?[0-9]+)" options:0 error:nil];
|
|
});
|
|
return twnTokenRegex;
|
|
}
|
|
|
|
+ (NSRegularExpression *)percentNumberRegex {
|
|
static dispatch_once_t onceToken;
|
|
static NSRegularExpression *percentNumberRegex;
|
|
dispatch_once(&onceToken, ^{
|
|
percentNumberRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:[%%])(:?[0-9s])" options:0 error:nil];
|
|
});
|
|
return percentNumberRegex;
|
|
}
|
|
|
|
+ (NSRegularExpression *)iOSTokenRegex {
|
|
static dispatch_once_t onceToken;
|
|
static NSRegularExpression *iOSTokenRegex;
|
|
dispatch_once(&onceToken, ^{
|
|
iOSTokenRegex = [NSRegularExpression regularExpressionWithPattern:@"%([0-9]*)\\$?([@dDuUxXoOfeEgGcCsSpaAF])" options:0 error:nil];
|
|
});
|
|
return iOSTokenRegex;
|
|
}
|
|
|
|
- (void)assertLprojFiles:(NSArray *)lprojFiles withTranslationStringsInDirectory:(NSString *)directory haveNoMatchesWithRegex:(NSRegularExpression *)regex {
|
|
XCTAssertNotNil(regex);
|
|
for (NSString *lprojFileName in lprojFiles) {
|
|
if (![TWNStringsTests localeForLprojFilenameIsAvailableOniOS:lprojFileName]) {
|
|
continue;
|
|
}
|
|
NSDictionary *stringsDict = [self getTranslationStringsDictFromLprogAtPath:[directory stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in stringsDict) {
|
|
NSString *localizedString = stringsDict[key];
|
|
NSTextCheckingResult *result = [regex firstMatchInString:localizedString options:0 range:NSMakeRange(0, localizedString.length)];
|
|
XCTAssertNil(result, @"Invalid character in string: %@ for key: %@ in locale: %@", localizedString, key, lprojFileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)assertLprojFiles:(NSArray *)lprojFiles withTranslationStringsInDirectory:(NSString *)directory doesNotContain:(NSString *)banned {
|
|
XCTAssertNotNil(banned);
|
|
NSString * bannedUpper = [banned uppercaseString];
|
|
for (NSString *lprojFileName in lprojFiles) {
|
|
if (![TWNStringsTests localeForLprojFilenameIsAvailableOniOS:lprojFileName]) {
|
|
continue;
|
|
}
|
|
NSDictionary *stringsDict = [self getTranslationStringsDictFromLprogAtPath:[directory stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in stringsDict) {
|
|
NSString *localizedString = stringsDict[key];
|
|
BOOL doesContainBannedString = [[localizedString uppercaseString] containsString:bannedUpper];
|
|
XCTAssertFalse(doesContainBannedString, @"Invalid substring %@ found in: %@ for key: %@ in locale: %@", banned, localizedString, key, lprojFileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)testiOSTranslationStringForTWNSubstitutionShortcuts {
|
|
[self assertLprojFiles:TWNStringsTests.iOSLprojFiles withTranslationStringsInDirectory:TWNStringsTests.bundleRoot haveNoMatchesWithRegex:TWNStringsTests.twnTokenRegex];
|
|
}
|
|
|
|
- (void)testIncomingTranslationStringForReversedSubstitutionShortcuts {
|
|
[self assertLprojFiles:TWNStringsTests.twnLprojFiles withTranslationStringsInDirectory:TWNStringsTests.twnLocalizationsDirectory haveNoMatchesWithRegex:TWNStringsTests.reverseTWNTokenRegex];
|
|
}
|
|
|
|
- (void)testiOSTranslationStringForReversedSubstitutionShortcuts {
|
|
[self assertLprojFiles:TWNStringsTests.iOSLprojFiles withTranslationStringsInDirectory:TWNStringsTests.bundleRoot haveNoMatchesWithRegex:TWNStringsTests.reverseiOSTokenRegex];
|
|
}
|
|
|
|
- (void)testIncomingTranslationStringForPercentTokens {
|
|
[self assertLprojFiles:TWNStringsTests.twnLprojFiles withTranslationStringsInDirectory:TWNStringsTests.twnLocalizationsDirectory haveNoMatchesWithRegex:TWNStringsTests.percentNumberRegex];
|
|
}
|
|
|
|
+ (NSRegularExpression *)htmlTagRegex {
|
|
static NSRegularExpression *htmlTagRegex;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
htmlTagRegex = [NSRegularExpression regularExpressionWithPattern:@"(<[^>]*>)([^<]*)" options:NSRegularExpressionCaseInsensitive error:nil];
|
|
});
|
|
return htmlTagRegex;
|
|
}
|
|
|
|
- (void)testIncomingTranslationStringForHTML {
|
|
[self assertLprojFiles:TWNStringsTests.twnLprojFiles withTranslationStringsInDirectory:TWNStringsTests.bundleRoot haveNoMatchesWithRegex:TWNStringsTests.htmlTagRegex];
|
|
}
|
|
|
|
- (void)testIncomingTranslationStringForNBSP {
|
|
[self assertLprojFiles:TWNStringsTests.twnLprojFiles withTranslationStringsInDirectory:TWNStringsTests.bundleRoot doesNotContain:@" "];
|
|
}
|
|
|
|
- (void)testIncomingTranslationStringForBracketSubstitutions {
|
|
for (NSString *lprojFileName in TWNStringsTests.twnLprojFiles) {
|
|
if (![lprojFileName isEqualToString:@"qqq.lproj"]) {
|
|
NSDictionary *stringsDict = [self getTranslationStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
NSDictionary *pluralizableStringsDict = [self getPluralizableStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in stringsDict) {
|
|
NSString *localizedString = stringsDict[key];
|
|
if ([localizedString containsString:@"{{"]) {
|
|
NSString *lowercaseString = localizedString.lowercaseString;
|
|
if ([lowercaseString containsString:@"{{plural:"]) {
|
|
XCTAssertNotNil([pluralizableStringsDict objectForKey:key], @"Localizable string %@ in %@ with PLURAL: needs an entry in the corresponding stringsdict file. This likely means that this language's Localizable.stringsdict hasn't been added to the project yet.", key, lprojFileName);
|
|
} else if (![lowercaseString containsString:@"{{formatnum:$"]) {
|
|
XCTAssertTrue(false, @"%@ in %@ has unsupported {{ }} in localization.", key, lprojFileName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)testiOSTranslationStringForBracketSubstitutionsAndMismatchedTokens {
|
|
NSDictionary *enStrings = [self getTranslationStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:@"en.lproj"]];
|
|
NSMutableDictionary *enTokensByKey = [NSMutableDictionary dictionaryWithCapacity:enStrings.count];
|
|
for (NSString *lprojFileName in TWNStringsTests.iOSLprojFiles) {
|
|
if (![lprojFileName isEqualToString:@"qqq.lproj"]) {
|
|
NSDictionary *stringsDict = [self getTranslationStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
NSDictionary *pluralizableStringsDict = [self getPluralizableStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in stringsDict) {
|
|
NSString *localizedString = stringsDict[key];
|
|
if ([localizedString containsString:@"{{"]) {
|
|
NSString *lowercaseString = localizedString.lowercaseString;
|
|
if ([lowercaseString containsString:@"{{plural:%"]) {
|
|
XCTAssertNotNil([pluralizableStringsDict objectForKey:key], @"Localizable string %@ in %@ with PLURAL: needs an entry in the corresponding stringsdict file. This likely means that this language's Localizable.stringsdict hasn't been added to the project yet.", key, lprojFileName);
|
|
|
|
} else {
|
|
XCTAssertTrue(false, @"Unsupported {{ }} in localization");
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary *localizedTokens = [NSMutableDictionary new];
|
|
NSRegularExpression *tokenRegex = [TWNStringsTests iOSTokenRegex];
|
|
[tokenRegex enumerateMatchesInString:localizedString
|
|
options:0
|
|
range:NSMakeRange(0, localizedString.length)
|
|
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) {
|
|
NSString *tokenKey = [tokenRegex replacementStringForResult:result inString:localizedString offset:0 template:@"$1"];
|
|
if ([tokenKey isEqualToString:@""]) {
|
|
tokenKey = @"1";
|
|
XCTAssertNil(localizedTokens[tokenKey], @"There can only be one unordered token in a localization string. Switch to ordered tokens:\n%@\n%@", key, localizedString);
|
|
}
|
|
NSString *value = [tokenRegex replacementStringForResult:result inString:localizedString offset:0 template:@"$2"];
|
|
localizedTokens[tokenKey] = value;
|
|
}];
|
|
|
|
NSString *enString = enStrings[key];
|
|
NSMutableDictionary *enTokens = enTokensByKey[key];
|
|
if (enString) {
|
|
if (!enTokens) {
|
|
enTokens = [NSMutableDictionary new];
|
|
[tokenRegex enumerateMatchesInString:enString
|
|
options:0
|
|
range:NSMakeRange(0, enString.length)
|
|
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) {
|
|
NSString *tokenKey = [tokenRegex replacementStringForResult:result inString:enString offset:0 template:@"$1"];
|
|
if ([tokenKey isEqualToString:@""]) {
|
|
tokenKey = @"1";
|
|
XCTAssertNil(enTokens[tokenKey], @"There can only be one unordered token in a localization string. Switch to ordered tokens:\n%@\n%@", key, enString);
|
|
}
|
|
NSString *value = [tokenRegex replacementStringForResult:result inString:enString offset:0 template:@"$2"];
|
|
enTokens[tokenKey] = value;
|
|
}];
|
|
enTokensByKey[key] = enTokens;
|
|
}
|
|
|
|
XCTAssertEqualObjects(localizedTokens, enTokens, @"%@ translation for %@ has incorrect tokens:\n%@\n%@", lprojFileName, key, enString, localizedString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSArray *)unbundledLprojFiles {
|
|
NSMutableArray *files = [TWNStringsTests.twnLprojFiles mutableCopy];
|
|
[files removeObjectsInArray:TWNStringsTests.bundledLprojFiles];
|
|
return files;
|
|
}
|
|
|
|
- (NSArray *)unbundledLprojFilesWithTranslations {
|
|
// unbundled lProj's containing "Localizable.strings"
|
|
return
|
|
[self.unbundledLprojFiles wmf_select:^BOOL(NSString *lprojFileName) {
|
|
BOOL isDirectory = NO;
|
|
NSString *localizableStringsFilePath =
|
|
[[TWNStringsTests.twnLocalizationsDirectory stringByAppendingPathComponent:lprojFileName] stringByAppendingPathComponent:@"Localizable.strings"];
|
|
return [[NSFileManager defaultManager] fileExistsAtPath:localizableStringsFilePath isDirectory:&isDirectory];
|
|
}];
|
|
}
|
|
|
|
+ (NSSet<NSString *> *)supportedLocales {
|
|
static dispatch_once_t onceToken;
|
|
static NSSet<NSString *> *supportedLocales;
|
|
dispatch_once(&onceToken, ^{
|
|
NSArray *lowercaseAvailableLocales = [[NSLocale availableLocaleIdentifiers] wmf_map:^id(NSString *locale) {
|
|
return [locale lowercaseString];
|
|
}];
|
|
supportedLocales = [NSSet setWithArray:lowercaseAvailableLocales];
|
|
});
|
|
return supportedLocales;
|
|
}
|
|
|
|
+ (BOOL)localeForLprojFilenameIsAvailableOniOS:(NSString *)lprojFileName {
|
|
NSString *localeIdentifier = [[lprojFileName substringToIndex:lprojFileName.length - 6] lowercaseString]; //remove .lproj suffix
|
|
return [[TWNStringsTests supportedLocales] containsObject:localeIdentifier];
|
|
}
|
|
|
|
- (void)testAllSupportedTranslatedLanguagesWereAddedToProjectLocalizations {
|
|
// Fails if any supported languages have translations (in "Localizable.strings") but are
|
|
// not yet bundled in the project.
|
|
// So, if this test fails, the languages listed will need to be added these to the project's localizations.
|
|
NSArray *files = [self.unbundledLprojFilesWithTranslations mutableCopy];
|
|
for (NSString *file in files) {
|
|
XCTAssert(![TWNStringsTests localeForLprojFilenameIsAvailableOniOS:file], @"Missing supported translation for %@", file);
|
|
}
|
|
}
|
|
|
|
- (void)testKeysForUnderscores {
|
|
for (NSString *lprojFileName in TWNStringsTests.twnLprojFiles) {
|
|
NSDictionary *stringsDict = [self getTranslationStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in stringsDict) {
|
|
// Keys use dash "-" separators.
|
|
XCTAssertFalse([key containsString:@"_"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Translators need context for all substitutions. If a string has any substitutions, such as "$1" or "$2" etc, the string's comment needs to explain what will be substituted in place of "$1", "$2" etc.
|
|
- (void)testiOSTranslationCommentsForMentionOfEachSubstitution {
|
|
NSDictionary *enStrings = [self getDictFromPListAtPath:[[TWNStringsTests.twnLocalizationsDirectory stringByAppendingPathComponent:@"en.lproj"] stringByAppendingPathComponent:@"Localizable.strings"]];
|
|
NSDictionary *qqqStrings = [self getDictFromPListAtPath:[[TWNStringsTests.twnLocalizationsDirectory stringByAppendingPathComponent:@"qqq.lproj"] stringByAppendingPathComponent:@"Localizable.strings"]];
|
|
for (NSString *enKey in enStrings) {
|
|
// This test assumes each EN key is also present in QQQ.
|
|
XCTAssertTrue([qqqStrings valueForKey:enKey], @"Expected en key in qqq");
|
|
|
|
NSString *enString = enStrings[enKey];
|
|
NSString *qqqString = qqqStrings[enKey];
|
|
NSArray<NSTextCheckingResult *> *enSubstitutionMatches = [TWNStringsTests.twnTokenRegex matchesInString:enString options:0 range:NSMakeRange(0, enString.length)];
|
|
NSArray<NSTextCheckingResult *> *qqqSubstitutionMatches = [TWNStringsTests.twnTokenRegex matchesInString:qqqString options:0 range:NSMakeRange(0, qqqString.length)];
|
|
|
|
for (NSTextCheckingResult *enMatch in enSubstitutionMatches) {
|
|
NSString *enMatchString = [enString substringWithRange:enMatch.range];
|
|
BOOL didFindEnMatchStringAtLeastOnceInQQQMatchString = NO;
|
|
|
|
for (NSTextCheckingResult *qqqMatch in qqqSubstitutionMatches) {
|
|
NSString *qqqMatchString = [qqqString substringWithRange:qqqMatch.range];
|
|
if ([qqqMatchString isEqualToString:enMatchString]) {
|
|
didFindEnMatchStringAtLeastOnceInQQQMatchString = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
XCTAssertTrue(didFindEnMatchStringAtLeastOnceInQQQMatchString, @"\n\tExpected each substitution (i.e. \"$1\") in string is mentioned at least once in its comment.\n\t\tString: \"%@\"\n\t\tComment: \"%@\"\n\t\tKey: \"%@\"\n\n", [enString stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"], [qqqString stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"], enKey);
|
|
|
|
if (!didFindEnMatchStringAtLeastOnceInQQQMatchString) {
|
|
// No need keep testing if a string already failed our assertion once.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Translators have been know to add "{{plural..." syntax to strings which don't yet have "{{plural..." in EN, which means the string won't be correctly resolved.
|
|
- (void)testIncomingTranslationStringForBracketSubstitutionsNotPresentInEN {
|
|
NSDictionary *enPluralizableStringsDict = [self getPluralizableStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:@"en.lproj"]];
|
|
for (NSString *lprojFileName in TWNStringsTests.twnLprojFiles) {
|
|
if (![lprojFileName isEqualToString:@"qqq.lproj"] && ![lprojFileName isEqualToString:@"en.lproj"]) {
|
|
NSDictionary *translationPluralizableStringsDict = [self getPluralizableStringsDictFromLprogAtPath:[TWNStringsTests.bundleRoot stringByAppendingPathComponent:lprojFileName]];
|
|
for (NSString *key in translationPluralizableStringsDict) {
|
|
XCTAssertNotNil([enPluralizableStringsDict objectForKey:key], @"\n\n\"%@\" translation containing plurals syntax received for \"%@\" string. The original EN string...\n\thttps://translatewiki.net/w/i.php?title=Wikimedia:Wikipedia-ios-%@/en&action=edit\n...doesn't have (or possibly need) plural syntax - either plural syntax will need to be added to the EN string or the translation...\n\thttps://translatewiki.net/w/i.php?title=Wikimedia:Wikipedia-ios-%@/%@&action=edit\n...will need to be updated to remove plural syntax.\n(Note: after loading the link above you can tap the \"Ask question\" button to pre-fill a Phabricator ticket for asking `i18n` folks for assistance for this string)\n\n", lprojFileName, key, key, key, [lprojFileName stringByReplacingOccurrencesOfString:@".lproj" withString:@""]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)tearDown {
|
|
[super tearDown];
|
|
}
|
|
|
|
@end
|