Grammatical Agreement
You need grammatical agreement techniques when words in your app’s UI need to change their form based on another word. If you need to display information about an item, and there could be one or more of the item, you need both its singular and its plural forms, as in “1 apple” and “2 apples”. If your displayed string uses an interpolation like \(item.name), then you need a way to display the correct plural form of every possible value of item.name.
In languages like French and Spanish, every noun is either feminine or masculine, and any adjectives used with it must agree with its gender and number — “une chemise verte”, “deux chemises vertes”. This agreement extends to adjectives that appear later in the string or elsewhere on the screen.
Grammatical agreement involves at least two words:
- The word that must be agreed with, usually a variable or argument.
- One or more words that depend on this argument, changing their form to agree with it.
Using Agreement Attributes
There are three tools for grammatical agreement. Which one you use depends on the proximity of the dependent words to the argument:
-
Use
inflect: truewhen the words are close together in the same string, so you can contain them in a single block. This is the easiest way to match plural forms to numbers as in “1 child” and “2 children” or to match gender as in “une chemise” and “un chapeau”. -
Use
agreeWithArgument: <argument position>when the words are in separate blocks in the same string. Apple advises to keep blocks as short as possible to avoid ambiguity, so use this tool if the words are at opposite ends of the string. -
Use
agreeWithConcept: <argument position>when the dependent word is not in the same string as the argument, such as when your app presents a set of options — size or color — for the argument, and the gender of the options depends on the argument — pizza or clothing. You must useAttributedStringfor this tool becauseString.LocalizationOptionsdoesn’t have aconceptsproperty.
Each of these tools is an attribute in a Markdown extension.
Extending Markdown
Since iOS 15, you can use Markdown in Text views to easily display formatted text. You’re probably familiar with this syntax for creating a link:
[Kodeco](https://www.kodeco.com)
The anchor text is in brackets, followed by the URL in parentheses. This syntax has been extended for Automatic Grammar Agreement:
^[bleu %$@](inflect: true)
The syntax is the same as for a link but with the ^ symbol in front. The brackets contain the text that needs grammatical agreement, with the agreement attribute in parentheses.
Note: It’s best to apply Automatic Grammar Agreement Markdown attributes in the
Localizablestring catalogs. If you add an annotation to a string in the code editor, the string is different from the existing key, so it becomes a new key in the English string catalog, and the existing key becomes stale. In the string catalog of other languages, this new string doesn’t have a translation — you must copy-paste the now-stale translation, then edit it to add the annotation.
Inflecting
Inflection: A change in the form of a word (typically the ending) to express a grammatical function or attribute such as tense, mood, person, number, case, and gender.
Use the inflect attribute on a block — part of a string — that contains an argument that other words in the block should agree with grammatically. Add this Markdown directly to strings in Localizable:
^[%1$lld %2$@ %3$@](inflect: true)
This is the ClothesPicker localization string for “1 green shirt” or “2 green shirts”. In this case, the third word should change to plural when the first argument is more than 1. In French, the second word should agree with both the first argument — the number — and the gender of the third argument. And the second and third words should swap positions — “1 chemise verte”.
Agreeing With Argument
Use agreeWithArgument when a string contains a word and the argument it needs to agree with, but the word and the argument are separated by a lot of text. Again, annotate strings directly in Localizable:
C'est ^[un %@](inflect: true) d'homme ... ^[porté](agreeWithArgument: 1) ...
The value 1 of the number argument means the word “porté” should agree with the first argument in the string.
Agreeing With Concept
The new LocalizationOptions property concepts enables you to specify objects that affect grammatical agreement of a string but aren’t formatted into the string as an argument. Unlike the inflect and agreeWithArgument attributes, the new agreeWithConcept attribute requires some code changes.
First, you must create a LocalizationOptions struct and initialize its concepts array with a localizedPhrase concept for the string value you need to agree with — an item of food or clothing:
var options: AttributedString.LocalizationOptions {
var options = AttributedString.LocalizationOptions()
options.concepts = [.localizedPhrase(item.name)]
return options
}
Then, add the options parameter to strings that contain words — size or color — that must agree with this concept. These strings must be of type AttributedString to use the concepts property.
Finally, in Localizable, annotate the dependent word that needs inflection with agreeWithConcept:
^[noir](agreeWithConcept: 1)
^[bleu](agreeWithConcept: 1)
^[vert](agreeWithConcept: 1)
^[rouge](agreeWithConcept: 1)
The value 1 of the number argument is the concept’s index in the concepts array, which uses 1-based indexing.
This is all you need to make the dependent words agree with the concept’s gender and number.
Being Inclusive With TermsOfAddress
A different concept — termsOfAddress — enables your app to accommodate grammatical gender agreement. Your app automatically uses the preferred pronoun of the user or another person associated with your app, such as a delivery person or clothes model.
options.concepts = [.termsOfAddress(model.preferredTermsOfAddress)]
The TermsOfAddress API predefines feminine, masculine and neutral terms of address, but you can specify pronouns and language to create your own custom term of address.
As with localizedPhrase, you add the options parameter to strings that contain pronouns that must agree with the termsOfAddress concept. If necessary, edit these strings to be of type AttributedString:
Text(
AttributedString(
localized:
"🏃🏻➡️\(model.name) is \(model.height) cm tall. He wears size \(model.size).",
options: options))
Now, to annotate the pronoun in Localizable, you need the Markdown attribute. The termsOfAddress concept is the referent of the dependent pronoun, so its attribute is referentConcept. As with localizedPhrase, the value of its number argument is its index in the concepts array:
... ^[He](referentConcept: 1) ...
Enough theory — it’s time to look at some code!