{"title":"Manage singular and plural numeric translations","slug":"translation-pluralization","url":"https://support.storeconnect.com/articles/translation-pluralization","url_markdown":"https://support.storeconnect.com/articles/translation-pluralization.md","subtitle":null,"summary":"Select between singular and plural translation strings by passing a count to the t filter: key syntax, the six plural forms including exact counts and ranges, and adding a pluralized key as Locale Translation records.","type":"Developer_Documentation","video_url":"","keywords":"pluralization, plural, count, translation keys, t filter, locale translation, singular, theme locale, translations, localization, one, other, zero","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"## Overview\n\nA string that reports a number needs more than one wording. \"1 match\" and \"5 matches\" cannot come from the same translation value, and choosing between them in the template hard-codes English grammar into your theme, where no translator can reach it.\n\nThe `t` filter does the choosing instead. Give one key several values, one per plural form, then pass the number as `count` when you call the key. The filter returns the form that matches the number.\n\nThis topic is for theme developers writing Liquid templates, and for anyone maintaining **Locale Translation** records for a translated store.\n\n:::note\nStoreConnect has no `pluralize` filter. Pluralization is a feature of the `t` filter, described below. A template that pipes a number into `pluralize` produces nothing useful.\n:::\n\n## Syntax\n\nPluralization has two halves: how you name the keys, and how you call them.\n\n### Naming the keys\n\nAdd the plural form to the end of the key, after a dot:\n\n```text\n\n\u003cparent key\u003e.\u003cform\u003e\n```\n\nEach form is a separate **Locale Translation** record, and the records that share a parent key make up one group. This group ships with StoreConnect:\n\n| Key | Value |\n|------|-------|\n| `locations.search.results.count.zero` | `No matches` |\n| `locations.search.results.count.one` | `1 match` |\n| `locations.search.results.count.other` | `%{count} matches` |\n\n### Calling the key\n\nCall the parent key, never an individual form, and pass the number as `count`:\n\n\n```liquid\n\n{{ \"locations.search.results.count\" | t: count: found_locations.size }}\n```\n\n\nThe filter reads `count`, selects the form that matches it, and returns that value. Three results give you `3 matches`, one result gives you `1 match`, and none gives you `No matches`.\n\nBoth argument separators work, so `| t: count: n` and `| t, count: n` are equivalent. You will see both in the base theme.\n\n## Plural forms\n\nA group can hold any of the six forms below. The storefront checks them in the order listed and returns the first one that both matches the count and exists in the group, so the order you create the records in makes no difference.\n\n| Form | Selected when | Example key |\n|------|-------|-------|\n| `zero` | The count is `0` | `locations.search.results.count.zero` |\n| `one` | The count is `1` | `locations.search.results.count.one` |\n| `#N` | The count is exactly `N` | `locations.search.results.count.#2` |\n| `N_M` | The count falls anywhere in the inclusive range `N` to `M` | `locations.search.results.count.2_10` |\n| `infinity` | The count is unlimited | `locations.search.results.count.infinity` |\n| `other` | Nothing more specific matched | `locations.search.results.count.other` |\n\n`one` and `other` are the conventional plural forms. `zero`, `#N`, `N_M`, and `infinity` are StoreConnect additions, so a group copied from another platform will not use them.\n\nEvery form is optional, but a group holding nothing but `other` is the safest starting point, because it returns a value for any count.\n\n:::warning\nAlways include an `other` form. A count that matches no form in the group fails outright rather than falling back to the nearest match, so a group holding only `one` breaks as soon as the count reaches `2`.\n:::\n\n:::warning\nExact-count forms need the `#`. A key ending in digits alone, such as `locations.search.results.count.2`, is read as a list position rather than a form name, and the whole group breaks. Write `#2` instead. Range forms such as `2_10` are safe, because the underscore stops the segment being read as a number.\n:::\n\n## Interpolating the count\n\nWrite `%{count}` in a value and the number that selected the form is substituted into the text, as in the `other` form above.\n\nUse `%{count}` only where the number should be visible. A `one` form usually reads better with the number written out, as in `1 match` rather than `%{count} match`, and a `zero` form rarely needs it at all.\n\n## Worked examples\n\nThese groups all ship with StoreConnect and are useful patterns to copy.\n\nAn exact count that replaces a phrase, because English has a single word for two weeks:\n\n| Key | Value |\n|------|-------|\n| `products.pricing.timespan_week.count.one` | `week` |\n| `products.pricing.timespan_week.count.#2` | `fortnight` |\n| `products.pricing.timespan_week.count.other` | `%{count} weeks` |\n\nAn unlimited quantity, where a number would be wrong. Only two forms are needed:\n\n| Key | Value |\n|------|-------|\n| `accounts.product_approvals.shared.quantity.count.infinity` | `Unlimited` |\n| `accounts.product_approvals.shared.quantity.count.other` | `%{count}` |\n\nExact counts used as position labels rather than quantities. This group names each line of an address form, and falls back to a generic label past the fourth:\n\n| Key | Value |\n|------|-------|\n| `accounts.shared.address_form.address_lines.count.#1` | `Street address` |\n| `accounts.shared.address_form.address_lines.count.#2` | `Suite` |\n| `accounts.shared.address_form.address_lines.count.#3` | `Apartment` |\n| `accounts.shared.address_form.address_lines.count.#4` | `Building Number` |\n| `accounts.shared.address_form.address_lines.count.other` | `line %{count}` |\n\nThe template passes the loop position as the count:\n\n\n```liquid\n\n{%- assign field = form.fields[\"billing_address_lines\"] %}\n{%- for line in field.value %}\n  \u003clabel for=\"{{ field.id }}_{{ forloop.index }}\"\u003e\n    {{ \"accounts.shared.address_form.address_lines.count\" | t, count: forloop.index }}\n  \u003c/label\u003e\n{%- endfor %}\n```\n\n\n:::tip\nName the parent key `count`, as every group above does. The call site then reads `\"…orders.count\" | t: count: n`, which makes it obvious at a glance that the key is pluralized.\n:::\n\n## Add a pluralized key\n\n1.  Open the **Theme Locale** record for the locale you are translating.\n2.  Go to the **Locale Translations** related list.\n3.  Click **New**.\n4.  In **Key**, enter the full key including the plural form, such as `locations.search.results.count.one`.\n5.  In **Value**, enter the wording for that form, using `%{count}` where the number should appear.\n6.  Click **Save**.\n7.  Repeat for each form in the group, making sure one of them is `other`.\n\nThemes imported from a zip file supply the same records as rows in `translations/\u003clocale\u003e.default.csv`, one row per form:\n\n```csv\n\nKey,Value\nlocations.search.results.count.zero,No matches\nlocations.search.results.count.one,1 match\nlocations.search.results.count.other,%{count} matches\n```\n\n## Avoid these patterns\n\nBoth patterns below work in English and break in every other language. Replace them with a pluralized key.\n\nGrammar written into the template, where no translator can correct it:\n\n\n```liquid\n\n{{ n }} item{% if n != 1 %}s{% endif %}\n```\n\n\nSeparate keys selected by a condition, which is what makes a translation set grow faster than it needs to:\n\n\n```liquid\n\n{% if n == 1 %}{{ \"cart.one_item\" | t }}{% else %}{{ \"cart.n_items\" | t: count: n }}{% endif %}\n```\n"}