Skip to content

Commit

Permalink
Add localization tests to guarantee all localized keys have translati…
Browse files Browse the repository at this point in the history
…ons in all supported localizations
  • Loading branch information
oadebanwo-carbon committed Nov 29, 2023
1 parent 88bdab3 commit cc73b5a
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
12 changes: 12 additions & 0 deletions EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
EDFBE2842B16C40800EFB793 /* UIImageView+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE2832B16C40800EFB793 /* UIImageView+Animations.swift */; };
EDFBE2882B17B52700EFB793 /* FeedViewControllerTests+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE2872B17B52700EFB793 /* FeedViewControllerTests+Localization.swift */; };
EDFBE2892B17BA2100EFB793 /* Feed.strings in Resources */ = {isa = PBXBuildFile; fileRef = EDFBE28B2B17BA2100EFB793 /* Feed.strings */; };
EDFBE2902B17BD1C00EFB793 /* FeedLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -209,6 +210,7 @@
EDFBE28A2B17BA2100EFB793 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Feed.strings; sourceTree = "<group>"; };
EDFBE28C2B17BAEF00EFB793 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Feed.strings"; sourceTree = "<group>"; };
EDFBE28D2B17BBDF00EFB793 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Feed.strings; sourceTree = "<group>"; };
EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedLocalizationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -354,6 +356,7 @@
ED0EDEC62B0FB681004857B7 /* EssentialFeediOSTests */ = {
isa = PBXGroup;
children = (
EDFBE28E2B17BC8500EFB793 /* Feed Presentation */,
EDFBE2652B1572A800EFB793 /* Feed UI */,
);
path = EssentialFeediOSTests;
Expand Down Expand Up @@ -557,6 +560,14 @@
path = "Feed Presentation";
sourceTree = "<group>";
};
EDFBE28E2B17BC8500EFB793 /* Feed Presentation */ = {
isa = PBXGroup;
children = (
EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */,
);
path = "Feed Presentation";
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -877,6 +888,7 @@
files = (
EDFBE2472B1048CF00EFB793 /* XCTTestCase + MemoryLeakTracking.swift in Sources */,
EDFBE2642B15713C00EFB793 /* FeedViewControllerTests+Assertions.swift in Sources */,
EDFBE2902B17BD1C00EFB793 /* FeedLocalizationTests.swift in Sources */,
EDFBE2582B15005500EFB793 /* UIButton+TestHelpers.swift in Sources */,
EDFBE2562B14FFF500EFB793 /* UIRefreshControl+TestHelpers.swift in Sources */,
EDFBE25A2B1500F900EFB793 /* UIControl+TestHelpers.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// FeedLocalizationTests.swift
// EssentialFeediOSTests
//
// Created by Oluwaseun Adebanwo on 29/11/2023.
//

import XCTest
@testable import EssentialFeediOS

final class FeedLocalizationTests: XCTestCase {

func test_localizedStrings_haveKeysAndValuesForAllSupportedLocalizations() {
let table = "Feed"
let presentationBundle = Bundle(for: FeedPresenter.self)
let localizationBundles = allLocalizationBundles(in: presentationBundle)
let localizedStringKeys = allLocalizedStringKeys(in: localizationBundles, table: table)

localizationBundles.forEach { (bundle, localization) in
localizedStringKeys.forEach { key in
let localizedString = bundle.localizedString(forKey: key, value: nil, table: table)

if localizedString == key {
let language = Locale.current.localizedString(forLanguageCode: localization) ?? ""

XCTFail("Missing \(language) (\(localization)) localized string for key: '\(key)' in table: '\(table)'")
}
}
}
}

// MARK: - Helpers

private typealias LocalizedBundle = (bundle: Bundle, localization: String)

private func allLocalizationBundles(in bundle: Bundle, file: StaticString = #file, line: UInt = #line) -> [LocalizedBundle] {
return bundle.localizations.compactMap { localization in
guard
let path = bundle.path(forResource: localization, ofType: "lproj"),
let localizedBundle = Bundle(path: path)
else {
XCTFail("Couldn't find bundle for localization: \(localization)", file: file, line: line)
return nil
}

return (localizedBundle, localization)
}
}

private func allLocalizedStringKeys(in bundles: [LocalizedBundle], table: String, file: StaticString = #file, line: UInt = #line) -> Set<String> {
return bundles.reduce([]) { (acc, current) in
guard
let path = current.bundle.path(forResource: table, ofType: "strings"),
let strings = NSDictionary(contentsOfFile: path),
let keys = strings.allKeys as? [String]
else {
XCTFail("Couldn't load localized strings for localization: \(current.localization)", file: file, line: line)
return acc
}

return acc.union(Set(keys))
}
}
}

0 comments on commit cc73b5a

Please sign in to comment.