├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── GrammaticalNumber
│ ├── GrammaticalNumberRule.swift
│ └── StringExtensions.swift
└── Tests
├── GrammaticalNumberTests
├── GrammaticalNumberTests.swift
└── XCTestManifests.swift
└── LinuxMain.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | # *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # Accio dependency management
62 | Dependencies/
63 | .accio/
64 |
65 | # fastlane
66 | #
67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
68 | # screenshots whenever they are needed.
69 | # For more information about the recommended setup visit:
70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
71 |
72 | fastlane/report.xml
73 | fastlane/Preview.html
74 | fastlane/screenshots/**/*.png
75 | fastlane/test_output
76 |
77 | # Code Injection
78 | #
79 | # After new code Injection tools there's a generated folder /iOSInjectionProject
80 | # https://github.com/johnno1962/injectionforxcode
81 |
82 | iOSInjectionProject/
83 | .DS_Store
84 | .swiftpm
85 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Devran "Cosmo" Uenal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "GrammaticalNumber",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "GrammaticalNumber",
12 | targets: ["GrammaticalNumber"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "GrammaticalNumber",
23 | dependencies: []),
24 | .testTarget(
25 | name: "GrammaticalNumberTests",
26 | dependencies: ["GrammaticalNumber"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GrammaticalNumber
2 |
3 | Turning singular words to plural can be [very hard](https://en.wikipedia.org/wiki/English_plurals) in some spoken languages, while other languages have [simple rules](https://en.wikipedia.org/wiki/Turkish_grammar#Inflectional_suffixes).
4 |
5 | `GrammaticalNumber` is heavily inspired by [`ActiveSupport::Inflector`](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb) known from the [Ruby on Rails](https://rubyonrails.org) web framework.
6 |
7 |
8 | ## Usage
9 |
10 | ### Turn singular words to plural
11 |
12 | ```swift
13 | "person".pluralized() // people
14 | "center".pluralized() // centers
15 | "sheep".pluralized() // sheep
16 | "knife".pluralized() // knives
17 | "mouse".pluralized() // mice
18 | "money".pluralized() // money
19 | "axis".pluralized() // axes
20 | "item".pluralized() // items
21 | "status".pluralized() // statuses
22 | "fox".pluralized() // foxes
23 | "move".pluralized() // moves
24 | "tooth".pluralized() // teeth
25 | "foxes".pluralized() // foxes
26 | "millennium".pluralized() // millennia
27 | "child".pluralized() // children
28 | "matrix".pluralized() // matrices
29 | "man".pluralized() // men
30 | "ox".pluralized() // oxen
31 | "radius".pluralized() // radii
32 | "grandchild".pluralized() // grandchildren
33 | ```
34 |
35 | ### Turn plural words to singular
36 |
37 | ```swift
38 | "children".singularized() // child
39 | "tables".singularized() // table
40 | "computers".singularized() // computer
41 | "mice".singularized() // mouse
42 | "teeth".singularized() // tooth
43 | "axes".singularized() // axis
44 | "women".singularized() // woman
45 | "grandchildren".singularized() // grandchild
46 | ```
47 |
48 | ### Case Sensitivity
49 |
50 | `GrammaticalNumber` will try to match the letter casing of your input word.
51 | Lowercased, uppercased and capitalized words are supported.
52 |
53 | ```swift
54 | "tooth".pluralized() // teeth
55 | "TOOTH".pluralized() // TOOTH
56 | "Tooth".pluralized() // Teeth
57 | ```
58 |
59 | ### Add count to words
60 |
61 | Prepends the pluralized `String` with `count`.
62 | If the `count` is `0`, the singular word will be used.
63 |
64 | ```swift
65 | "child".pluralized(count: 0) // 0 children
66 | "child".pluralized(count: 1) // 1 child
67 | "child".pluralized(count: 3) // 3 children
68 |
69 | "knife".pluralized(count: 0) // 0 knives
70 | "knife".pluralized(count: 1) // 1 knife
71 | "knife".pluralized(count: 3) // 3 knives
72 |
73 | "sheep".pluralized(count: 0) // 0 sheep
74 | "sheep".pluralized(count: 1) // 1 sheep
75 | "sheep".pluralized(count: 3) // 3 sheep
76 | ```
77 |
78 | ## Define Custom Rules
79 |
80 | ### Uncountable Rule
81 |
82 | ```swift
83 | let rule: GrammaticalNumberRule = .uncountable("money")
84 | ```
85 |
86 | `money` will never change.
87 |
88 | ### Irregular Rule: Singular from plural
89 |
90 | ```swift
91 | let rule: GrammaticalNumberRule = .irregular("tooth", "teeth")
92 | ```
93 |
94 | Turns `tooth` to `teeth` when used with `pluralized()`.
95 | Turns `teeth` to `tooth` when used with `singularized()`.
96 |
97 | ### Plural Rule: Plural from singular with regular expression
98 |
99 | ```swift
100 | let rule: GrammaticalNumberRule = .plural(#"^(m|l)ouse$"#, #"$1ice"#)
101 | ```
102 |
103 | `mouse` becomes `mice` and `louse` becomes `lice`.
104 |
105 | ### Singular Rule: Singular from plural with regular expression
106 |
107 | ```swift
108 | let rule: GrammaticalNumberRule = .singular(#"(matr)ices$"#, #"$1ix"#)
109 | ```
110 |
111 | Turns `matrices` to `matrix`.
112 |
113 | ### Apply rule, so it becomes available
114 |
115 | ```swift
116 | GrammaticalNumberRule.add(rule)
117 | ```
118 |
119 | ## Support other languages
120 |
121 | In order to support other languages, pass the `language` parameter to your custom rules.
122 | Call `.pluralized(language: yourLanguage)` with the same `language` value — like so: `.pluralized(language: "tr")`
123 |
124 | ### Example for the turkish language (`tr`)
125 |
126 | ```swift
127 | GrammaticalNumberRule.add(.plural(#"([aoıu][^aoıueöiü]{0,6})$"#, #"$1lar"#), language: "tr")
128 | GrammaticalNumberRule.add(.plural(#"([eöiü][^aoıueöiü]{0,6})$"#, #"$1ler"#), language: "tr")
129 | GrammaticalNumberRule.add(.singular(#"l[ae]r$"#, #""#), language: "tr")
130 | ```
131 |
132 | ```swift
133 | "kitap".pluralized(language: "tr") // kitaplar
134 | "yemek".pluralized(language: "tr") // yemekler
135 | ```
136 |
137 | ## Contact
138 |
139 | * Devran "Cosmo" Uenal
140 | * Twitter: [@maccosmo](http://twitter.com/maccosmo)
141 | * LinkedIn: [devranuenal](https://www.linkedin.com/in/devranuenal)
142 |
143 | ## Other Projects
144 |
145 | * [BinaryKit](https://github.com/Cosmo/BinaryKit) — BinaryKit helps you to break down binary data into bits and bytes and easily access specific parts.
146 | * [Clippy](https://github.com/Cosmo/Clippy) — Clippy from Microsoft Office is back and runs on macOS! Written in Swift.
147 | * [HackMan](https://github.com/Cosmo/HackMan) — Stop writing boilerplate code yourself. Let hackman do it for you via the command line.
148 | * [ISO8859](https://github.com/Cosmo/ISO8859) — Convert ISO8859 1-16 Encoded Text to String in Swift. Supports iOS, tvOS, watchOS and macOS.
149 | * [SpriteMap](https://github.com/Cosmo/SpriteMap) — SpriteMap helps you to extract sprites out of a sprite map. Written in Swift.
150 | * [StringCase](https://github.com/Cosmo/StringCase) — Converts String to lowerCamelCase, UpperCamelCase and snake_case. Tested and written in Swift.
151 | * [TinyConsole](https://github.com/Cosmo/TinyConsole) — TinyConsole is a micro-console that can help you log and display information inside an iOS application, where having a connection to a development computer is not possible.
152 |
153 | ## License
154 |
155 | GrammaticalNumber is released under the [MIT License](http://www.opensource.org/licenses/MIT).
156 |
--------------------------------------------------------------------------------
/Sources/GrammaticalNumber/GrammaticalNumberRule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Devran on 31.08.19.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum GrammaticalNumberRule {
11 | case plural(_ rule: String, _ replacement: String)
12 | case singular(_ rule: String, _ replacement: String)
13 | case irregular(_ singular: String, _ plural: String)
14 | case uncountable(_ rule: String)
15 |
16 | public static var defaultLanguage = "en"
17 |
18 | public static func add(_ rule: GrammaticalNumberRule, language: String = defaultLanguage) {
19 | if rules[language] == nil {
20 | rules[language] = []
21 | }
22 | rules[language]?.append(rule)
23 | }
24 |
25 | public static func clear() {
26 | rules = [:]
27 | }
28 |
29 | public static var rules: [String: [GrammaticalNumberRule]] = [
30 | defaultLanguage: [
31 | .plural(#"$"#, "s"),
32 | .plural(#"s$"#, "s"),
33 | .plural(#"^(ax|test)is$"#, #"$1es"#),
34 | .plural(#"(octop|vir)us$"#, #"$1i"#),
35 | .plural(#"(octop|vir)i$"#, #"$1i"#),
36 | .plural(#"(alias|status)$"#, #"$1es"#),
37 | .plural(#"(bu)s$"#, #"$1ses"#),
38 | .plural(#"(buffal|tomat)o$"#, #"$1oes"#),
39 | .plural(#"([ti])um$"#, #"$1a"#),
40 | .plural(#"([ti])a$"#, #"$1a"#),
41 | .plural(#"sis$"#, "ses"),
42 | .plural(#"(?:([^f])fe|([lr])f)$"#, #"$1$2ves"#),
43 | .plural(#"(hive)$"#, #"$1s"#),
44 | .plural(#"([^aeiouy]|qu)y$"#, #"$1ies"#),
45 | .plural(#"(x|ch|ss|sh)$"#, #"$1es"#),
46 | .plural(#"(matr|vert|ind)(?:ix|ex)$"#, #"$1ices"#),
47 | .plural(#"^(m|l)ouse$"#, #"$1ice"#),
48 | .plural(#"^(m|l)ice$"#, #"$1ice"#),
49 | .plural(#"^(ox)$"#, #"$1en"#),
50 | .plural(#"^(oxen)$"#, #"$1"#),
51 | .plural(#"(quiz)$"#, #"$1zes"#),
52 |
53 | .singular(#"s$"#, ""),
54 | .singular(#"(ss)$"#, #"$1"#),
55 | .singular(#"(n)ews$"#, #"$1ews"#),
56 | .singular(#"([ti])a$"#, #"$1um"#),
57 | .singular(#"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$"#, #"$1sis"#),
58 | .singular(#"(^analy)(sis|ses)$"#, #"$1sis"#),
59 | .singular(#"([^f])ves$"#, #"$1fe"#),
60 | .singular(#"(hive)s$"#, #"$1"#),
61 | .singular(#"(tive)s$"#, #"$1"#),
62 | .singular(#"([lr])ves$"#, #"$1f"#),
63 | .singular(#"([^aeiouy]|qu)ies$"#, #"$1y"#),
64 | .singular(#"(s)eries$"#, #"$1eries"#),
65 | .singular(#"(m)ovies$"#, #"$1ovie"#),
66 | .singular(#"(x|ch|ss|sh)es$"#, #"$1"#),
67 | .singular(#"^(m|l)ice$"#, #"$1ouse"#),
68 | .singular(#"(bus)(es)?$"#, #"$1"#),
69 | .singular(#"(o)es$"#, #"$1"#),
70 | .singular(#"(shoe)s$"#, #"$1"#),
71 | .singular(#"(cris|test)(is|es)$"#, #"$1is"#),
72 | .singular(#"^(a)x[ie]s$"#, #"$1xis"#),
73 | .singular(#"(octop|vir)(us|i)$"#, #"$1us"#),
74 | .singular(#"(alias|status)(es)?$"#, #"$1"#),
75 | .singular(#"^(ox)en"#, #"$1"#),
76 | .singular(#"(vert|ind)ices$"#, #"$1ex"#),
77 | .singular(#"(matr)ices$"#, #"$1ix"#),
78 | .singular(#"(quiz)zes$"#, #"$1"#),
79 | .singular(#"(database)s$"#, #"$1"#),
80 |
81 | .irregular("person", "people"),
82 | .irregular("man", "men"),
83 | .irregular("child", "children"),
84 | .irregular("sex", "sexes"),
85 | .irregular("move", "moves"),
86 | .irregular("zombie", "zombies"),
87 | .irregular("radius", "radii"),
88 | .irregular("tooth", "teeth"),
89 | .irregular("goose", "geese"),
90 |
91 | .uncountable("equipment"),
92 | .uncountable("information"),
93 | .uncountable("rice"),
94 | .uncountable("money"),
95 | .uncountable("species"),
96 | .uncountable("series"),
97 | .uncountable("fish"),
98 | .uncountable("sheep"),
99 | .uncountable("jeans"),
100 | .uncountable("police")
101 | ]
102 | ]
103 | }
104 |
--------------------------------------------------------------------------------
/Sources/GrammaticalNumber/StringExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension String {
4 | private func prependCount(_ count: Int?) -> String {
5 | guard let count = count else { return self }
6 | return [String(count), self].joined(separator: " ")
7 | }
8 |
9 | public func pluralized(count: Int? = nil, language: String = GrammaticalNumberRule.defaultLanguage) -> String {
10 | var word = self
11 |
12 | if count == 1 {
13 | return word.matchCase(self).prependCount(count)
14 | }
15 |
16 | guard let rules = GrammaticalNumberRule.rules[language] else {
17 | return word.matchCase(self).prependCount(count)
18 | }
19 |
20 | guard let rule = (rules.reversed().first { grammaticalNumberRule -> Bool in
21 | switch grammaticalNumberRule {
22 | case .uncountable(let rule), .irregular(let rule, _):
23 | return self.lowercased().contains(rule.lowercased())
24 | case .plural(let rule, _):
25 | return self.range(of: rule, options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
26 | default: return false
27 | }
28 | }) else {
29 | return word.matchCase(self).prependCount(count)
30 | }
31 |
32 | switch rule {
33 | case .irregular(let rule, let replacement), .plural(let rule, let replacement):
34 | word = word.replacingOccurrences(of: rule, with: replacement, options: [.regularExpression, .caseInsensitive])
35 | default: break
36 | }
37 |
38 | return word.matchCase(self).prependCount(count)
39 | }
40 |
41 | public func singularized(language: String = GrammaticalNumberRule.defaultLanguage) -> String {
42 | var word = self
43 |
44 | guard let rules = GrammaticalNumberRule.rules[language] else {
45 | return self
46 | }
47 |
48 | guard let rule = (rules.reversed().first { grammaticalNumberRule -> Bool in
49 | switch grammaticalNumberRule {
50 | case .uncountable(let rule), .irregular(_, let rule):
51 | return self.lowercased().contains(rule.lowercased())
52 | case .singular(let rule, _):
53 | return self.range(of: rule, options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil
54 | default: return false
55 | }
56 | }) else {
57 | return word
58 | }
59 |
60 | switch rule {
61 | case .irregular(let replacement, let rule), .singular(let rule, let replacement):
62 | word = word.replacingOccurrences(of: rule, with: replacement, options: [.regularExpression, .caseInsensitive])
63 | default: break
64 | }
65 |
66 | return word.matchCase(self)
67 | }
68 |
69 | func matchCase(_ input: String) -> String {
70 | if input.allSatisfy({ $0.isUppercase }) {
71 | return self.uppercased()
72 | } else if input.allSatisfy({ $0.isLowercase }) {
73 | return self.lowercased()
74 | } else if let first = input.first, first.isUppercase {
75 | return self.capitalized
76 | }
77 | return self
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Tests/GrammaticalNumberTests/GrammaticalNumberTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import GrammaticalNumber
3 |
4 | final class GrammaticalNumberTests: XCTestCase {
5 | func testPlurals() {
6 | XCTAssertEqual("person".pluralized(), "people")
7 | XCTAssertEqual("center".pluralized(), "centers")
8 | XCTAssertEqual("sheep".pluralized(), "sheep")
9 | XCTAssertEqual("knife".pluralized(), "knives")
10 | XCTAssertEqual("mouse".pluralized(), "mice")
11 | XCTAssertEqual("money".pluralized(), "money")
12 | XCTAssertEqual("axis".pluralized(), "axes")
13 | XCTAssertEqual("item".pluralized(), "items")
14 | XCTAssertEqual("status".pluralized(), "statuses")
15 | XCTAssertEqual("fox".pluralized(), "foxes")
16 | XCTAssertEqual("move".pluralized(), "moves")
17 | XCTAssertEqual("tooth".pluralized(), "teeth")
18 | XCTAssertEqual("foxes".pluralized(), "foxes")
19 | XCTAssertEqual("millennium".pluralized(), "millennia")
20 | XCTAssertEqual("child".pluralized(), "children")
21 | XCTAssertEqual("matrix".pluralized(), "matrices")
22 | XCTAssertEqual("man".pluralized(), "men")
23 | XCTAssertEqual("ox".pluralized(), "oxen")
24 | XCTAssertEqual("radius".pluralized(), "radii")
25 | XCTAssertEqual("grandchild".pluralized(), "grandchildren")
26 | }
27 |
28 | func testSingulars() {
29 | XCTAssertEqual("children".singularized(), "child")
30 | XCTAssertEqual("tables".singularized(), "table")
31 | XCTAssertEqual("computers".singularized(), "computer")
32 | XCTAssertEqual("mice".singularized(), "mouse")
33 | XCTAssertEqual("teeth".singularized(), "tooth")
34 | XCTAssertEqual("axes".singularized(), "axis")
35 | XCTAssertEqual("women".singularized(), "woman")
36 | XCTAssertEqual("grandchildren".singularized(), "grandchild")
37 | }
38 |
39 | func testCounts() {
40 | XCTAssertEqual("child".pluralized(count: 0), "0 children")
41 | XCTAssertEqual("child".pluralized(count: 1), "1 child")
42 | XCTAssertEqual("child".pluralized(count: 3), "3 children")
43 |
44 | XCTAssertEqual("knife".pluralized(count: 0), "0 knives")
45 | XCTAssertEqual("knife".pluralized(count: 1), "1 knife")
46 | XCTAssertEqual("knife".pluralized(count: 3), "3 knives")
47 |
48 | XCTAssertEqual("sheep".pluralized(count: 0), "0 sheep")
49 | XCTAssertEqual("sheep".pluralized(count: 1), "1 sheep")
50 | XCTAssertEqual("sheep".pluralized(count: 3), "3 sheep")
51 | }
52 |
53 | func testCustomRules() {
54 | let rule = GrammaticalNumberRule.plural("bug", "features")
55 | GrammaticalNumberRule.add(rule)
56 |
57 | XCTAssertEqual("bug".pluralized(), "features")
58 | }
59 |
60 | func testCustomGermanRules() {
61 | let rule = GrammaticalNumberRule.plural("album", "alben")
62 | GrammaticalNumberRule.add(rule, language: "de")
63 |
64 | XCTAssertEqual("album".pluralized(language: "de"), "alben")
65 | }
66 |
67 | func testCustomTurkishRules() {
68 | GrammaticalNumberRule.add(.plural(#"([aoıu][^aoıueöiü]{0,6})$"#, #"$1lar"#), language: "tr")
69 | GrammaticalNumberRule.add(.plural(#"([eöiü][^aoıueöiü]{0,6})$"#, #"$1ler"#), language: "tr")
70 | GrammaticalNumberRule.add(.singular(#"l[ae]r$"#, #""#), language: "tr")
71 |
72 | XCTAssertEqual("koltuk".pluralized(language: "tr"), "koltuklar")
73 | XCTAssertEqual("kitap".pluralized(language: "tr"), "kitaplar")
74 | XCTAssertEqual("domates".pluralized(language: "tr"), "domatesler")
75 | XCTAssertEqual("yemek".pluralized(language: "tr"), "yemekler")
76 | XCTAssertEqual("yemekler".singularized(language: "tr"), "yemek")
77 | XCTAssertEqual("dolaplar".singularized(language: "tr"), "dolap")
78 | }
79 |
80 | func testCaseSensitivity() {
81 | XCTAssertEqual("matrix".pluralized(), "matrices")
82 | XCTAssertEqual("Matrix".pluralized(), "Matrices")
83 | XCTAssertEqual("MATRIX".pluralized(), "MATRICES")
84 |
85 | XCTAssertEqual("sheep".pluralized(count: 0), "0 sheep")
86 | XCTAssertEqual("Sheep".pluralized(count: 0), "0 Sheep")
87 | XCTAssertEqual("SHEEP".pluralized(count: 0), "0 SHEEP")
88 |
89 | XCTAssertEqual("knife".pluralized(count: 0), "0 knives")
90 | XCTAssertEqual("Knife".pluralized(count: 0), "0 Knives")
91 | XCTAssertEqual("KNIFE".pluralized(count: 0), "0 KNIVES")
92 |
93 | XCTAssertEqual("teeth".singularized(), "tooth")
94 | XCTAssertEqual("Teeth".singularized(), "Tooth")
95 | XCTAssertEqual("TEETH".singularized(), "TOOTH")
96 |
97 | XCTAssertEqual("child".pluralized(), "children")
98 | XCTAssertEqual("Child".pluralized(), "Children")
99 | XCTAssertEqual("CHILD".pluralized(), "CHILDREN")
100 | }
101 |
102 | func testCaseMatching() {
103 | XCTAssertEqual("child".matchCase("CHILD"), "CHILD")
104 | XCTAssertEqual("Child".matchCase("Child"), "Child")
105 | XCTAssertEqual("children".matchCase("Children"), "Children")
106 | }
107 |
108 | static var allTests = [
109 | ("testPlurals", testPlurals),
110 | ("testSingulars", testSingulars),
111 | ("testCounts", testCounts),
112 | ("testCustomRules", testCustomRules),
113 | ("testCustomGermanRules", testCustomGermanRules),
114 | ("testCustomTurkishRules", testCustomTurkishRules),
115 | ("testCaseSensitivity", testCaseSensitivity),
116 | ("testCaseMatching", testCaseMatching),
117 | ]
118 | }
119 |
--------------------------------------------------------------------------------
/Tests/GrammaticalNumberTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(GrammaticalNumberTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import GrammaticalNumberTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += GrammaticalNumberTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------