Skip to content

Add Catalan (ca) locale - #1675

Open
losimo wants to merge 7 commits into
getfider:mainfrom
losimo:catala-pr
Open

losimo wants to merge 7 commits into
getfider:mainfrom
losimo:catala-pr

Conversation

@losimo

@losimo losimo commented Sep 27, 2026 •

Copy link
Copy Markdown

Hi,

This adds Catalan to Fider. It follows locale/README.md: the two catalogues plus the four registration points (the locale.go constant and AllLocales, locales.ts, lingui.config.js, ssr.tsx).

All 323 strings are translated and reviewed against the Softcatalà style guide, which is the reference Catalan software localisation follows.

The three fields on the Locale struct I checked rather than guessed:

  • MessageFormatCode: "ca" exists in gotnospirit/makeplural at the commit pinned in go.mod (199 CLDR cultures), so NewWithCulture("ca") resolves.
  • PostgresConfig: "simple" because PostgreSQL ships no catalan text search configuration in the versions Fider runs against. I checked on postgres:12; postgres:17 does have one, but simple is the safe floor.
  • lingua.Catalan exists in lingua-go v1.4.0.

One thing worth flagging, since it's a choice and not a mechanical translation.
The English catalogue calls the same object three different things depending on the string: "idea" in ten of them, "suggestion" in three, "post" in the rest.

Catalan uses one word throughout, so the interface reads consistently. It seemed better than carrying the variation across, but say the word if you'd rather it mirrored the English.

I ran make build to check the registration points compile and that the strings end up in the SSR bundle, and I've had it running on a live instance for a day.

Related: #1674, about the admin pages, which this doesn't touch.

Catalan has no locale in Fider yet. This adds one, following locale/README.md:
the two catalogues plus the four registration points (the locale.go constant and
AllLocales, locales.ts, lingui.config.js, ssr.tsx).

All 323 strings are translated and reviewed against the Softcatalà style guide,
which is the reference Catalan software localisation follows.

The three fields on the Locale struct were each checked rather than guessed:

- MessageFormatCode "ca" exists in gotnospirit/makeplural at the commit pinned
  in go.mod (199 CLDR cultures), so NewWithCulture("ca") resolves.
- PostgresConfig is "simple" because PostgreSQL ships no "catalan" text search
  configuration in the versions Fider runs against. I checked on postgres:12;
  postgres:17 does have one, but "simple" is the safe floor.
- lingua.Catalan exists in lingua-go v1.4.0.

One note on terminology. The English catalogue calls the same object "idea",
"suggestion" and "post" depending on the string. Catalan uses one word for all
three, so the interface reads consistently. That is more consistent than the
original rather than a departure from it, and it seemed better than carrying the
variation across.

I ran make build to check the four registration points compile and that the
strings end up in the SSR bundle.
The catalogue called the same thing six different ways: «suggeriment» 27
times, «entrada» 9, «post» 7, «missatge» 5, «publicació» 4 and «article» 2.
This fixes the 14 strings where the English says "post" and the Catalan said
something else, so that all of them now say «suggeriment» — which is what the
neighbouring strings already said («moderation.post.publish.error»,
«showpost.moderation.declineerror», «email.footer.subscription_notice2»).

Three of them also fix something else, found while checking:

- «Post aprovat amb èxit» → «El suggeriment s'ha aprovat correctament».
  "amb èxit" is a calque of "successfully"; the sibling strings for comments
  already use "correctament".
- Two notification messages used the informal second person («Rebràs») where
  the application addresses the user, which the Catalan style guide writes in
  the formal form («Rebreu»). One string, «email.footer.subscription_notice»,
  mixed both in the same sentence.
- «està sent referenciada per» is an English passive; Catalan puts it in the
  active voice.

@mattwoberts mattwoberts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this! The wiring is right. The locale.go fields check out (ca exists in makeplural, lingua.Catalan exists in v1.4.0, and simple is the right Postgres config; the SQL search mapping falls back to simple anyway). I built it merged onto current main (Go build, lingui compile, SSR bundle) and every server string parses and renders.

The catalogue does need another pass, though. A fair number of strings still look like unreviewed machine translation, and a few break the UI. Grouped by severity:

Broken markup or wrong content

  • mysettings.apikey.documentation: "…llegiu la documentació oficial <0></0>." The <0> is empty, so the documentation link renders with no text. It should wrap the text: "…llegiu la <0>documentació oficial</0>."
  • email.delete_post.text (server): "<strong>{title}</strong> ha estat eliminat <strong></strong>." Empty <strong>; "eliminat" should be inside it.
  • modal.deleteaccount.text: the tag structure has changed. In English <2> is nested inside <1> (<1>This process is irreversible. <2>Are you sure?</2></1>), but the Catalan closes <1> first and puts <2> after it. The <2> text also says "Escriviu el vostre correu electrònic per confirmar-ho", but this dialog has no email field, just a Confirm button. It should be something like <1>Aquest procés és irreversible. <2>N'esteu segur?</2></1>.
  • mysettings.message.avatar.custom says "200×200 píxels", but the English (and the code) say 50×50.

Wrong meaning

key current issue
error.internalerror.title Dispara! literal "fire!" (as in a gun); e.g. "Vaja!" / "Ostres!"
moderation.empty Tots esteu atrapats! "you're all trapped"; e.g. "Ja ho teniu tot al dia!"
menu.signout Tanca means "Close"; "Tanca la sessió"
action.confirm Confirmada past participle; "Confirma"
label.letter Carta a posted letter; this is the avatar type, so "Lletra"
property.key Tecla keyboard key; "Clau"
action.commentsfeed Font del comentari "source of the comment"; "Canal de comentaris"
mynotifications.label.readrecently Llegiu els darrers 30 dies. an instruction to read; "Llegides els darrers 30 dies."
error.expired.title Caducitat noun; "Caducat"
header.nav.feedback Tots els comentaris collides with actual comments; e.g. "Tots els suggeriments" to match postdetails.backtoall

Word order

  • validation.maxstringlen: "menys de caràcters {len}" → "menys de {len} caràcters"
  • validation.custom.maxattachments (client.json): "Es permeten un màxim d'adjunts {number}." → "Es permet un màxim de {number} adjunts." (the server.json one is already right)
  • home.lonely.suggestion: "creeu suggeriments <0>com a mínim 3</0>" → "creeu <0>com a mínim 3</0> suggeriments"

Consistency

  • "post" terminology: the description says one word is used throughout, but "missatges" (home.postfilter.option.myposts, home.postscontainer.label.viewmore), "entrada" (home.lonely.text) and "publicacions" (roadmap.blank.description, email.delete_account_requested.text) are still there.
  • Register: most strings use vós, but several use tu (modal.signin.header, modal.rss.title, label.subscribe, modal.deletecomment.text, mynotifications.page.subtitle, newpost.modal.description.placeholder, newpost.modal.title.placeholder, signin.message.emailsent, signin.code.locked, showpost.commentinput.placeholder, roadmap.blank.description, email.delete_account_requested.cancel, which mixes both in one sentence) and a few use vostè (mysettings.dangerzone.notice "estigui segur", email.footer.noreply "no respongui", modal.rss.description "subscriure's").
  • email.subscription.change / email.subscription.unsubscribe are capitalised ("Canvieu…", "Cancel·la…"), but they're inserted mid-sentence into email.footer.subscription_notice ("Podeu {view}, {unsubscribe} o {change}"), so they should be lowercase and in the same form as email.subscription.view ("vegeu-lo…").

Small things

  • linkmodal.url.placeholder: https://exemple.com is a real, registered domain. Please keep https://example.com.
  • server.json is missing validation.custom.maximagepixels and validation.custom.imageprocessingbusy (added to en recently).
  • app/models/enum/locale.go: the // AllLocales contains all supported locales comment now sits above LocaleCatalan; please move it back above AllLocales.
  • Please also add ca to the language list in the Makefile locale-reset target. It isn't mentioned in locale/README.md (our omission), but it has its own hard-coded list.

On the terminology question: using one word throughout is fine by me, and better than copying the English inconsistency.

Thanks again, happy to take another look once it's updated.

Every item checked against the string it names.

Broken markup:

- mysettings.apikey.documentation had an empty <0>, so the documentation link
  rendered with no text. The text now sits inside the tag.
- email.delete_post.text had an empty <strong>; "eliminat" is now its content.
- modal.deleteaccount.text closed <1> before <2> instead of nesting them, and
  its <2> asked the reader to type an email address, which this dialog has no
  field for. It now reads "<1>Aquest procés és irreversible. <2>N'esteu
  segur?</2></1>".
- mysettings.message.avatar.custom said 200x200 where the English and the code
  say 50x50.

Word order, where the placeholder sat after the noun it counts:
validation.maxstringlen, validation.custom.maxattachments (the client.json one;
the server.json one was already right) and home.lonely.suggestion.

Terminology: the five remaining strings where the English says "posts" in the
plural still used «missatges», «entrada» or «publicacions». They now say
«suggeriment» like the rest. roadmap.blank.description also moves to the formal
imperative, since it is the application addressing the user.

email.subscription.change and email.subscription.unsubscribe are inserted
mid-sentence into email.footer.subscription_notice, so they lose the initial
capital and take the same form as email.subscription.view.

linkmodal.url.placeholder is back to https://example.com. exemple.com is a
registered domain, so the placeholder pointed readers at an unrelated site.
RFC 2606 reserves example.com for this.

Two things outside the catalogues: the "// AllLocales contains all supported
locales" comment is back above AllLocales, and the Makefile locale-reset target
now lists ca.
validation.custom.maximagepixels and validation.custom.imageprocessingbusy were
added to the English catalogue after this branch was opened and came in with the
merge from main. Both catalogues are complete again: 245 and 80 strings.

Neither carries "si us plau" for "Please". Catalan software interfaces drop it,
which is what the Softcatalà style guide prescribes and what the rest of this
catalogue already does.
@losimo

losimo commented Sep 30, 2026

Copy link
Copy Markdown
Author

Thanks for the detail. The grouping by severity made this easy to work through,
and every item below was checked against the string it names. The branch is now
merged onto current main, so the two catalogues are complete again at 245 and
80 strings.

Fixed

Broken markup

mysettings.apikey.documentation now wraps the text:
"…llegiu la <0>documentació oficial</0>."

email.delete_post.text:
"<strong>{title}</strong> ha estat <strong>eliminat</strong>."

modal.deleteaccount.text: <2> sits inside <1> again, matching the English,
and the text no longer asks for an email address:
"…</0><1>Aquest procés és irreversible. <2>N'esteu segur?</2></1>". Good catch
on that one. The dialog has only a Confirm button.

mysettings.message.avatar.custom says 50×50, as the English and the code do.

Word order

All three, plus the verb agreement in validation.custom.maxattachments, which
now reads the same as the server.json one.

Consistency

The five remaining post strings are now «suggeriment»:
home.postfilter.option.myposts, home.postscontainer.label.viewmore,
home.lonely.text, roadmap.blank.description and
email.delete_account_requested.text. roadmap.blank.description also moves to
the formal imperative, since it is the application addressing the user, so it
covers one of your register items too.

email.subscription.change and email.subscription.unsubscribe are lowercase
and in the same imperative form as email.subscription.view, since all three are
inserted mid-sentence.

The two missing keys

validation.custom.maximagepixels and validation.custom.imageprocessingbusy
came in with the merge and are translated. Neither carries «si us plau» for
"Please": Catalan software interfaces drop it, which is what the rest of this
catalogue already does.

Small things

linkmodal.url.placeholder is back to https://example.com. You were right to
flag it, and the consequence is worse than the style: exemple.com is a
registered domain, so the placeholder pointed readers at an unrelated site. We
ran the same check over another large catalogue afterwards and found four more of
them, so thanks for that one. It has become a check we keep.

app/models/enum/locale.go: the // AllLocales contains all supported locales
comment is back above AllLocales.

Makefile: ca added to the locale-reset list.

One we would like to keep as it is

label.subscribe → «Subscriu-t'hi». You list it with the tu strings that
should become vós.

The Catalan style guide we follow splits the treatment by who is addressing whom.
When the computer addresses the user it takes the vós imperative; when the user
commands the computer it takes tu. label.subscribe is a button label, so tu
is the prescribed form, for the same reason postdetails.backtoall reads «Torna
a tots els suggeriments» and not «Torneu».

Still open

Your other register items are right and we are working through them, including
the three vostè ones (mysettings.dangerzone.notice, email.footer.noreply,
modal.rss.description). That form does not appear in Catalan software
interfaces at all.

All eight items in the "wrong meaning" table are genuine, and none of them is a
mechanical fix, so they are going through our terminology review: «Sign out» →
«Tanca la sessió», «Key» → «Clau», «Letter» →
«Lletra», and the rest. We will align header.nav.feedback with
postdetails.backtoall as you suggest.

Both will come as a second push, and we will ping you.

Thanks again for building it merged onto main and for checking the locale.go
fields.

Terminology, with the source for each. The Softcatalà English-Catalan dictionary
splits these by domain and the domain settles them:

- Key: "clau", not "tecla". The dictionary gives "clau, contrasenya" under
  informàtica and "tecla" under música and tecnologia. property.key is an API
  key property. Our own catalogues already agreed: of 33 English strings with
  "key", 32 said "clau" and this was the one that did not.
- Comment Feed: "Canal de comentaris". The dictionary gives "canal de continguts"
  under internet. Three of the five "feed" strings here already said "canal";
  action.postsfeed also moves to "Canal de suggeriments", since it said
  "publicacions".
- Letter: "Lletra". It is the avatar type, and the neighbouring string already
  said "un avatar de lletres a partir de les vostres inicials".
- Sign out: "Tanca la sessió". "Tanca" alone reads as Close and does not say
  what it closes.
- All Feedback: "Tots els suggeriments", matching postdetails.backtoall. The
  dictionary does give "comentaris" for feedback, so this is not a language
  point: it is that "comentari" is already taken by "comment" in this product,
  and the main menu read as "all the comments people left" when it means "all
  the feedback people gave".

Meaning:

- Shoot! is an interjection, so "Ostres!" and not "Dispara!", which was the
  order to fire a weapon.
- You're all caught up means nothing is pending. "Tots esteu atrapats!" said the
  opposite of the good news.
- Read on last 30 days is the participle, so "Llegides", not the imperative
  "Llegiu", which asked the reader to spend thirty days reading.
- Expired qualifies what has expired, so "Caducat" and not the noun "Caducitat".
- Confirm is an action label, so the imperative "Confirma" and not the participle
  "Confirmada".

Register. The Catalan style guide writes the application addressing the user in
the formal imperative, and these thirteen used the informal or the third person:
modal.signin.header, modal.rss.title, modal.rss.description,
modal.deletecomment.text, mynotifications.page.subtitle,
newpost.modal.description.placeholder, newpost.modal.title.placeholder,
signin.message.emailsent, signin.code.locked,
showpost.commentinput.placeholder, email.delete_account_requested.cancel,
mysettings.dangerzone.notice and email.footer.noreply. Three of them also drop
"si us plau" for "Please", which Catalan interfaces leave out because the
imperative already makes the request.

email.delete_account_requested.cancel mixed both persons in one sentence, and
"canviar d'opinió" is not pronominal in Catalan, so the pronoun goes.

One found while checking, not in the review: page.pendingactivation.text2 said
"activar-la", agreeing with the inbox, when what gets activated is the site, as
the string right before it says.

label.subscribe stays as "Subscriu-t'hi". It is a button label, and the style
guide writes those in the informal imperative.
@losimo

losimo commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

The rest is in. Everything you listed is now addressed except label.subscribe, which we explained above and are keeping.

Terminology

The Softcatalà English-Catalan dictionary splits each of these by domain, and the domain settles them.

property.key → «Clau». The dictionary gives «clau, contrasenya» under informàtica and «tecla» under música and tecnologia. This is an API key property. Our own catalogues already agreed: of 33 English strings with "key"
across three products, 32 said «clau» and this was the one that did not.

action.commentsfeed → «Canal de comentaris», from «canal de continguts» under internet. Three of the five "feed" strings here already said «canal». action.postsfeed moves too: it said «Canal de publicacions» and now says «Canal de suggeriments». You did not list that one; it turned up while fixing its sibling.

label.letter → «Lletra», and the string next to it already said «un avatar de lletres a partir de les vostres inicials».

menu.signout → «Tanca la sessió», which is also the form the dictionary's own usage examples show for the menu item.

header.nav.feedback → «Tots els suggeriments», matching postdetails.backtoall. Worth being precise here: the dictionary does give «comentaris» for feedback, so this is not a language point. It is that «comentari» is already taken by comment in this product, and the main menu read as "all the comments people left" when it
means "all the feedback people gave". Your word for it, "collides", was the right one.

Meaning

error.internalerror.title → «Ostres! Això no ho esperàvem…». "Shoot!" is an interjection and «Dispara!» was the order to fire a weapon.

moderation.empty → «Ja ho teniu tot al dia!». "You're all caught up" means nothing is pending, and «Tots esteu atrapats!» said the opposite of the good news the string carries.

mynotifications.label.readrecently → «Llegides els darrers 30 dies.». The imperative asked the reader to spend thirty days reading.

error.expired.title → «Caducat». "Expired" qualifies what has expired; «Caducitat» is the name of the fact.

action.confirm → «Confirma». It is an action label, so the imperative.

Register

All thirteen: modal.signin.header, modal.rss.title, modal.rss.description, modal.deletecomment.text, mynotifications.page.subtitle, newpost.modal.description.placeholder, newpost.modal.title.placeholder,
signin.message.emailsent, signin.code.locked, showpost.commentinput.placeholder, email.delete_account_requested.cancel, mysettings.dangerzone.notice and email.footer.noreply.

Three of them also drop «si us plau» for "Please". Catalan interfaces leave it out, because the imperative already makes the request.

email.delete_account_requested.cancel had both persons in the same sentence, as you noted, and «canviar d'opinió» is not pronominal in Catalan, so the pronoun goes with it.

One more we found

page.pendingactivation.text2 said «activar-la», agreeing with the inbox, when what gets activated is the site, as the string right before it says. Not on your list; our own style check pointed at the string and reading it turned up the
pronoun.

State

The branch is on current main, both catalogues are complete at 245 and 80 strings, and every string that changed was checked against the source it translates.

Thanks for the review. It was specific enough to act on line by line, and two of your items turned into checks we now run over every catalogue.

Three strings the review did not list. Our own style and typography checks
pointed at them while going over the catalogue at the end.

page.pendingactivation.resending and showpost.loading said "Reenviant…" and
"Carregant…". The Catalan style guide writes software progress messages with the
periphrasis, so they now read "S'està reenviant…" and "S'està carregant…".

modal.showvotes.query.placeholder had three defects in one string. It wrote the
ellipsis as three dots instead of the single "…" character, which the style guide
settles and which was the most repeated defect we have measured anywhere. It used
the informal imperative where a field's help text has the application addressing
the user, so it now matches "Deixeu un comentari" in
showpost.commentinput.placeholder. And the third came out with the second: the
spell checker was flagging the agreement of "usuaris" because with "Cerca" in
front it read the noun as a postponed adjective. With "Cerqueu" that goes away.

Neither catalogue now writes an ellipsis with three dots.
@losimo

losimo commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

Three more, none of them on your list. Our own style and typography checks pointed at them while going over the catalogue at the end.

page.pendingactivation.resending and showpost.loading now read «S'està reenviant…» and «S'està carregant…». The Catalan style guide writes software progress messages with the periphrasis rather than the bare gerund.

modal.showvotes.query.placeholder had three defects in one string, and the third was a consequence of the second. It wrote the ellipsis as three dots instead of the single «…» character, which is the most repeated defect we have measured in any catalogue. It used the informal imperative where a field's help text has the application addressing the user, so it now matches «Deixeu un comentari» in showpost.commentinput.placeholder. And with «Cerca» in front, the spell checker was reading «usuaris» as a postponed adjective and complaining about its
agreement; with «Cerqueu» that goes away.

Neither catalogue now writes an ellipsis with three dots.

@mattwoberts

Copy link
Copy Markdown
Contributor

Thanks, this all looks good now. I went back through everything and it's all in. Happy with keeping «Subscriu-t'hi» too, that reasoning makes sense and it matches the other button labels.

One last thing, and it's my fault: the three email.subscription.* strings only ever get dropped in after «Podeu…» in the email footer, so they need to be infinitives, not imperatives. I pointed you at «vegeu-lo» as the model, which was already wrong. Sorry about that. Should be:

  • email.subscription.view → «veure'l al vostre navegador»
  • email.subscription.unsubscribe → «cancel·lar-ne la subscripció»
  • email.subscription.change → «canviar les preferències de notificació»

Optional, while you're in there: email.footer.subscription_notice starts with «Esteu rebent…» but _notice2 and _notice3 say «Rebeu…».

Once that's in I'll merge it 👍

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants