In the previous lesson, you used string catalog techniques to implement regular plurals (just add “s”) and create a not-quite-right French localization. In this lesson, you’ll:
Continue with your finished project from Lesson 3 or open the project in 04-grammatical-agreement/Starter.
Edit the project’s scheme to set App Language to English.
“2 blue pantss” — with the second “s” — is wrong. Grammatical agreement can fix this easily!
Inflecting
Pluralization
You’ll start by using inflection instead of Vary by Plural.
Hold the Option key while you open Localizable so it opens alongside ContentView. In the English string catalog, open the stepper label key to review its Vary by Plural settings. Select the One row and delete it. Then, delete the Other row and the “s” at the end of the remaining string.
Now, edit this string:
^[%1$lld %2$@ %3$@](inflect: true)
You’re using markdown to apply inflection to this string.
Build the project and refresh the preview. Try different item names to see how they pluralize correctly: It works for Sheep, Child, Woman and Pair of Pants. “Pants” becomes “Pant” when there’s just one.
Change the item back to “Shirt”.
Open the French string catalog The corresponding translation NEEDS REVIEW, so copy-paste the inflected string and build the project and regain the green check mark.
Switch App Language to French and try it out.
And now the color pluralizes correctly for more than one chemise. And the color’s gender matches the item’s gender.
Grammar Agreement doesn’t automatically change the word order to “chemises noires” — you must switch the arguments manually, as before.
Build the project then refresh the preview and try out your plural.
Gender
There’s one more place you can use inflect — in the item description, to make “un” agree with the gender of the item argument. Edit the string:
C'est ^[un %@](inflect: true) d'homme qui n'est jamais aussi bien porté que par une femme.
You do this Markdown only in the French string catalog because the English string doesn’t need this inflection.
Build the project and then refresh the preview. And you’ve got it: “C’est une chemise”!
Agreeing With Argument
There’s another place in this string that needs gender agreement. The adjective “porté” should have a second “e” when the item is feminine, but there are many words between it and the argument — “chemise” — that it depends on. Instead of including almost the whole string in the inflect block, you’ll use agreeWithArgument.
Edit the string:
C'est ^[un %@](inflect: true) d'homme qui n'est jamais aussi bien ^[porté](agreeWithArgument: 1) que par une femme.
In this case, the argument number is 1 because porté depends on the first argument — %@ — in this string.
Refresh the preview to see your description in perfect French!
Agreeing With Concept
Now, the only missing agreement is the color button labels: For a feminine item, “rouge” is OK, but the other three colors should all get a final “e” to match the item’s gender. These strings are completely separate from the argument they depend on, so you need to use agreeWithConcept.
Close the assistant editor to give you room to code. In ContentView, add this computed property:
var options: AttributedString.LocalizationOptions {
var options = AttributedString.LocalizationOptions()
options.concepts = [.localizedPhrase(item.name)]
return options
}
String.LocalizationOptions doesn’t have a concepts property, so you must use the AttributedString structure.
Now, in the code editor, pass options to each of the color strings passed to ColorButton:
color: AttributedString(localized: "red", options: options, comment: "color")
color: AttributedString(localized: "blue", options: options, comment: "color")
color: AttributedString(localized: "green", options: options, comment: "color")
color: AttributedString(localized: "black", options: options, comment: "color")
You must change these strings to AttributedString because you’re passing the options.concepts property.
Now, scroll down to ColorButton and fix the color declaration:
let color: AttributedString
To fix the other error, colorChoice must also be an AttributedString. Set this here, in ColorButton:
@Binding var colorChoice: AttributedString?
And also in ContentView:
@State var colorChoice: AttributedString?
There’s one more step: In Localizable, mark each French color string with agreeWithConcept:
^[noir](agreeWithConcept: 1)
^[bleu](agreeWithConcept: 1)
^[vert](agreeWithConcept: 1)
^[rouge](agreeWithConcept: 1)
This time, the argument 1 means the word must agree with the first concept in options.concept — in this case, the localized item.name.
Build the project, refresh the preview, and enjoy your French-localized app!
TermsOfAddress
Close the assistant editor and switch App Language back to English for your final task. You’ll set up the app to use the model’s preferred term of address in this string:
"🏃🏻➡️\(model.name) is \(model.height) cm tall. He wears size \(model.size)."
The text assumes the model identifies as male, but not all the models do. In fact, Sam prefers to be addressed as a woman, so set this in her preferredTermsOfAddress:
let model = Model(name: "Sam",
height: 170, size: "M",
preferredTermsOfAddress: [.feminine])
The type of preferredTermsOfAddress is an array of TermOfAddress, which has three predefined static properties — feminine, masculine, and neutral.
To get grammatical gender agreement between Sam’s preference and the pronoun in the string, you need another concept. Add this to options.concepts:
options.concepts = [
.localizedPhrase(item.name),
.termsOfAddress(model.preferredTermsOfAddress)]
Now, edit the string to use this new concept:
Text(
AttributedString(
localized: "🏃🏻➡️\(model.name) is \(model.height) cm tall. He wears size \(model.size).",
options: options),
comment: "Description of model")
To use concepts, the string must be an AttributedString with localized and options parameters.
Now, in the Localizable English string catalog, mark “He” to refer to the second concept in concepts:
^[He](referentConcept: 2)
In the French string catalog, mark “Il” the same way:
^[Il](referentConcept: 2)
If you had annotated “He” in the code editor, the string would be different from the existing key. It would become a new key in Localizable, and the existing key would become stale.
In the French string catalog, the new string wouldn’t have a translation — you’d have to copy-paste the now-stale string and then edit it to annotate “Il”.
Build the project and refresh the preview to see that “He” is now “She”. So far, so good, but try this — change Sam’s preferred term of address to .neutral:
let model = Model(name: "Sam",
height: 170, size: "M",
preferredTermsOfAddress: [.neutral])
You might’ve expected “She” to change to “They”, but it falls back to “He”! You could’ve started off with a gender-neutral string — “They wear size M” — but then you would’ve gotten “He wear size M” and “She wear size M”. And, if you try enclosing ^[They](referentConcept: 2) wear in an inflect: true markdown, neither markdown works. So take care in the wording of text around gender pronouns and be sure to test all the cases.