# Handling plural forms in activity pub localization strings

**URL:** https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687
**Category:** Translations
**Tags:** activitypub
**Created:** [April 27, 2025, 8:14pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687 "2025-04-27T20:14:32Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [April 27, 2025, 8:14pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/1 "2025-04-27T20:14:32Z")

</div>

I noticed some strings in the ActivityPub plugin that, in my opinion, need pluralization.

#### The first post of a new topic will be published %{delay\_minutes} minutes after being posted

[discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L105)  
If `delay_minutes` is 1, it should say “1 minute”.

#### “Publish Post #%{post\_number} and deliver it to the followers of the Group Actors in %{minutes} minutes.”

[discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L248)  
Same as above.

#### “Publish %{count} unpublished posts in Topic #%{topic\_id}. Posts will not be delivered to the followers of the Group Actors.”

[discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L293)

If only **1 post** is unpublished, you would say “Publish %{count} unpublished post in Topic #%{topic\_id}. Post will not be delivered to the followers of the Group Actors.”

#### “%{min\_length} to %{max\_length} letters, numbers, dashes or underscores.”

[discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L42)  
I think this one depends on `max_length`. If `max_length` is 1, you would say “letter, number, dash or underscore”. But I am not sure if there are other languages where `min_length` is relevant too.

#### “Username must be %{min\_length} to %{max\_length} letters, numbers, dashes or underscores.”

[discourse-activity-pub/config/locales/server.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/server.en.yml#L156)  
Same as above.

#### “%{count} of %{total} posts in this topic are published.”

[discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L279)  
This one is a little more complex because it depends on two variables: `count` and `total`. For example:

- **1** of _1_ _post_ in this topic **is** published
- **1** of _3_ _posts_ in this topic **is** published
- **2** of _3_ _posts_ in this topic **are** published

---

<div class="post-metadata">

### Author: ![pmusaraj](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pmusaraj/32/119489_2.png) [@pmusaraj](https://meta.discourse.org/u/pmusaraj)
#### Post date: [November 3, 2025, 4:10pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/7 "2025-11-03T16:10:23Z")

</div>

Sorry for the delay, Moin, this should address most items: [DEV: Update pluralization for some strings by pmusaraj · Pull Request #262 · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/pull/262)

> [@Moin](#):
>
> “%{min\_length} to %{max\_length} letters, numbers, dashes or underscores.”

> [@Moin](#):
>
> “Username must be %{min\_length} to %{max\_length} letters, numbers, dashes or underscores.”

For these two, I am leaving the strings as is. A site would be misconfigured if it had usernames limited to 1 character… no need to change.

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 3, 2025, 4:15pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/8 "2025-11-03T16:15:13Z")

</div>

What about sites with a username with 11 characters? There are languages where the version for 1 is also used for 11. And how does it work for languages with more than one plural if you don’t offer one in English? Can translators still enter those different plural versions?

---

<div class="post-metadata">

### Author: ![pmusaraj](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pmusaraj/32/119489_2.png) [@pmusaraj](https://meta.discourse.org/u/pmusaraj)
#### Post date: [November 3, 2025, 6:01pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/9 "2025-11-03T18:01:03Z")

</div>

> [@Moin](#):
>
> There are languages where the version for 1 is also used for 11.

Hmm, would 11 be considered singular in these languages?

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 3, 2025, 6:17pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/10 "2025-11-03T18:17:08Z")

</div>

There are languages that use the version for “one” on numbers ending with one. (I am not sure 11 was the best example). And they also have more versions than “one” and “other”, so even with only different counts greater than 1, those need different translations based on the exact count.

German isn’t one of them, so I guess some of your colleagues will know better than me.

> <https://github.com/discourse/discourse/blob/3bdf7d78f958f38bd417d2da2a4e7e32110e0c24/config/locales/plurals.rb#L2369-L2404>

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 3, 2025, 10:48pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/11 "2025-11-03T22:48:28Z")

</div>

> <https://github.com/discourse/discourse-activity-pub/blob/cad2ab6bd15e302759472e52baa3f316975ff8c0/config/locales/client.en.yml#L302-L304>

I am struggling a little with the translation: Does Discourse choose the singular and plural based on `count` or `total`?  
From my understanding, it’s usually count.  
But I cannot figure out how to handle the case that **`count` is 1** and **`total` is 3**. I get something like “1 of 3 _post_ in this topic is published”. I can fix that by using “posts”: “1 of 3 post_s_ in this topic is published”. But then it doesn’t work if `total` is 1. 🤯

> [@Moin](#):
>
> #### “%{count} of %{total} posts in this topic are published.”
> 
> [discourse-activity-pub/config/locales/client.en.yml at a49ec5c4f5acac28bafbc1c57c57161c067e8f6e · discourse/discourse-activity-pub · GitHub](https://github.com/discourse/discourse-activity-pub/blob/a49ec5c4f5acac28bafbc1c57c57161c067e8f6e/config/locales/client.en.yml#L279)  
> This one is a little more complex because it depends on two variables: `count` and `total`. For example:
> 
> - **1** of _1_ _post_ in this topic **is** published
> - **1** of _3_ _posts_ in this topic **is** published
> - **2** of _3_ _posts_ in this topic **are** published

Maybe we need [Message Format support for localization](https://meta.discourse.org/t/message-format-support-for-localization/7035) in this case?

---

<div class="post-metadata">

### Author: ![Jagster](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jagster/32/192154_2.png) [@Jagster](https://meta.discourse.org/u/Jagster)
#### Post date: [November 4, 2025, 12:05am UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/12 "2025-11-04T00:05:02Z")

</div>

> [@Moin](#):
>
> **1** of _1_ _post_ in this topic **is** published

That is something what we call enginer english. Such form should never be used. It should be something like `the/this post is published`. Or `topic` if possible following posts will be automatic published.

Similar situation, in Finnish anyway, is number one (actually every number between 0 .. 9) should be written, not using numbers. But quite common consept is write `one` instead of `1` and bigger ones are numbers.

Something like `1 post` is everytime doomed something what english speaking devs use, because they are too lazy to use human style — almost sorry to say that in loud. And in more generally: it is seen as american way than Using This Way In Headers, Texts 😏

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 4, 2025, 12:15am UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/13 "2025-11-04T00:15:52Z")

</div>

> [@Jagster](#):
>
> Something like `1 post` is everytime doomed something what english speaking devs use, because they are too lazy to use human style

It’s not about being lazy. Using the placeholder for the number is very important. As I explained above, there are languages where you also say “21 post”, just like you say “1 post”. If the English text used “one” instead of `%{count}`, it’s very easy for a translator to translate that, and then the number won’t be replaced by the actual count, which results in “one post” when the count is actually 21.  
So, there is a reason why the numbers are used. See also: [Always use %{count} variable when translating pluralized strings](https://meta.discourse.org/t/always-use-count-variable-when-translating-pluralized-strings/83969)

By the way, it’s also very helpful in German because it avoids problems like [this](https://meta.discourse.org/t/fehler-in-der-deutschen-ubersetzung-hier-melden/47320/81).

---

<div class="post-metadata">

### Author: ![Jagster](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jagster/32/192154_2.png) [@Jagster](https://meta.discourse.org/u/Jagster)
#### Post date: [November 4, 2025, 12:34am UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/14 "2025-11-04T00:34:15Z")

</div>

I have hard times to believe that coding is in such low level that it is impossible to replace `%(count)` to word when `%(count) = 1`. I’m quite sure I’ve changed such placeholder to pure text when local translating is giving different options for single and plural.

That is what I mean about lazyness and enginer english. Under the hood must use variables and placeholders, but those should be transformed to human and native read form. After all, notorius end users are using softwares, not enginers…

Yeah, I know. That is wider ang bigger question than just considering how to translate `1 of 3…` or `1 of 1…`. But my deepest point is there shouldn’t be need to wonder such things (outside other differences of zillion languages).

But sure, maybe we are living in the world where we can have complex AIs but there is no solution to avoid situations like `1 of 1` 😂

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 4, 2025, 1:16am UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/15 "2025-11-04T01:16:11Z")

</div>

It’s quite off-topic as this topic was about allowing any way of having a different singular and plural version for these texts.

But maybe you could replace `1` with _“one”_ everywhere in the interface in English.  
However, that won’t work as easily in German. The problem is that in German, the word for “one” changes depending on **case, gender, and grammatical role**.

For example:

- The topic will remain open for **one month**. → _Das Thema ist noch **einen Monat** geöffnet._
- The topic will be closed in **one month**. → _Das Thema wird in **einem Monat** geschlossen._
- **One month** has passed. → _ **Ein Monat** ist vergangen._

So while it’s also common in German to write out numbers as words, handling this correctly is **grammatically complex**.  
You can’t just replace the number “1” with the word “eins,” because that form is **never** used before a noun and the right word form depends on the grammatical case.

In other words, using words instead of digits would create a lot more work for translators.  
You couldn’t simply use a placeholder like `%{duration}` that expands to “one month,” “one week,” or “one day” in all contexts.  
Instead of three durations and three sentences, you’d need a separate version for every possible grammatical combination - potentially multiplying the amount of text several times.

---

<div class="post-metadata">

### Author: ![pmusaraj](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pmusaraj/32/119489_2.png) [@pmusaraj](https://meta.discourse.org/u/pmusaraj)
#### Post date: [November 4, 2025, 1:18pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/16 "2025-11-04T13:18:15Z")

</div>

> [@Moin](#):
>
> German isn’t one of them, so I guess some of your colleagues will know better than me.

Thanks for the link. I can see from reading the code and looking at other languages like arabic that languages can provide their own overrides even if the English string is the same across the variants. So for example, Arabic has separate rules when the count is 2, and I see `two: ` used in strings in Arabic that aren’t defined in English. So for 1/11, the language that has a rule can provide that variant. No need to define it in English from what I can tell.

> [@Moin](#):
>
> Maybe we need [Message Format support for localization](https://meta.discourse.org/t/message-format-support-for-localization/7035) in this case?

Thanks for digging into this one. I don’t have the context for this string, but if you want to submit a PR with the change, I’m happy to have a look. Thanks.

---

<div class="post-metadata">

### Author: ![Moin](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/moin/32/554653_2.png) [@Moin](https://meta.discourse.org/u/Moin)
#### Post date: [November 4, 2025, 1:48pm UTC](https://meta.discourse.org/t/handling-plural-forms-in-activity-pub-localization-strings/363687/17 "2025-11-04T13:48:47Z")

</div>

> [@pmusaraj](#):
>
> I can see from reading the code and looking at other languages like arabic that languages can provide their own overrides even if the English string is the same across the variants. So for example, Arabic has separate rules when the count is 2, and I see `two: ` used in strings in Arabic that aren’t defined in English. So for 1/11, the language that has a rule can provide that variant. No need to define it in English from what I can tell.

Can you explain how this can be done in Crowdin?  
This is what I see in Crowdin if I want to translate `discourse_activity_pub.actor.warning.invalid_username` into Arabic

 ![Screenshot_20251104_143419_Chrome](https://global.discourse-cdn.com/meta/original/4X/9/c/b/9cbbd4baa8526242daef9e602cd87c874e13535c.jpeg)

I have seen the menu for entering the plural versions before. For example on `js.user.password.too_short`

 ![Screenshot_20251104_143245_Chrome](https://global.discourse-cdn.com/meta/original/4X/8/6/9/8697c3920fcd3141f69be5db1ea3c0a12f06a0a6.jpeg)

But, in my experience, you can only add these different versions if the plurals are defined in English too.

> [@pmusaraj](#):
>
> I don’t have the context for this string

Me neither. The last time I had a look at the texts of ActivityPub was in April when I created this topic. Now, I was simply replacing the placeholders during translation because otherwise it’s very difficult to choose the words. That was when I noticed that “1 of 3 **post** in this topic is published” seemed wrong to me - not only in German but also in English.
