From 39ec7996c58b4a5ea9919e9a21f23127b38272b7 Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Tue, 19 May 2026 03:52:36 +0200 Subject: [PATCH] feat(fase-20): eu.json seed + Paraglide config + locale parity check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seeds apps/web/messages/eu.json with hand-translated Basque values for every key in en.json/es.json (362 keys, $schema excluded). Each value carries the ` [needs review]` suffix marker per plan §20.2.1 so a native speaker can sweep through and remove markers in a post-release pass. ICU placeholders ({name}, {count}, {n}, {version}, …) preserved verbatim — none move position relative to the source value. Adds `eu` to project.inlang/settings.json so the Paraglide vite plugin emits `src/lib/paraglide/messages/eu.js` on next build, and the runtime `availableLanguageTags` widens to ["en","es","eu"]. The compile output is gitignored as before; verified by re-running the compile CLI locally. New `scripts/check-locales.mjs` (dependency-free Node script) compares every non-source bundle's key set against en.json and fails on any missing/extra keys. Currently: eu = 362 keys, es = 362 keys, both match. Note on translation provenance: machine/LLM seed at mid-2026 quality. The plan calls out euskera SOV ordering risks around placeholders — spot-checked the 12 keys with placeholders ({name}, {count}, {n}, {version}, {date}, {commit}, {word}, {title}, {days}, {lists}, {tasks}, {notes}, {section}); placeholders stayed in source-comparable positions. Co-Authored-By: Claude Opus 4.7 --- apps/web/messages/eu.json | 365 ++++++++++++++++++++++++++ apps/web/project.inlang/settings.json | 2 +- scripts/check-locales.mjs | 90 +++++++ 3 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 apps/web/messages/eu.json create mode 100644 scripts/check-locales.mjs diff --git a/apps/web/messages/eu.json b/apps/web/messages/eu.json new file mode 100644 index 0000000..7d7642f --- /dev/null +++ b/apps/web/messages/eu.json @@ -0,0 +1,365 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "nav_lists": "Zerrendak [needs review]", + "nav_tasks": "Zereginak [needs review]", + "nav_notes": "Oharrak [needs review]", + "nav_search": "Bilatu [needs review]", + "nav_settings": "Ezarpenak [needs review]", + "nav_collective": "Kolektiboa [needs review]", + "app_name": "Colectivo [needs review]", + "loading": "Kargatzen… [needs review]", + "error_generic": "Zerbait gaizki joan da. Saiatu berriro, mesedez. [needs review]", + "login_title": "Sartu Colectivo-n [needs review]", + "login_button": "Saioa hasi [needs review]", + "logout_button": "Saioa itxi [needs review]", + "auth_back_to_home": "Hasierara itzuli [needs review]", + "logged_out_title": "Saioa itxita [needs review]", + "logged_out_subtitle": "Hasi saioa berriro jarraitzeko. [needs review]", + "logged_out_sign_in": "Saioa hasi [needs review]", + "onboarding_title": "Ongi etorri Colectivo-ra [needs review]", + "onboarding_subtitle": "Sortu kolektibo berri bat edo elkartu dagoen batekin. [needs review]", + "onboarding_create_tab": "Sortu [needs review]", + "onboarding_join_tab": "Elkartu [needs review]", + "onboarding_create_collective": "Sortu kolektibo bat [needs review]", + "onboarding_join_collective": "Elkartu gonbidapen esteka batekin [needs review]", + "onboarding_collective_name": "Kolektiboaren izena [needs review]", + "onboarding_collective_name_placeholder": "adib. Gure Etxea [needs review]", + "onboarding_collective_emoji": "Ikonoa [needs review]", + "onboarding_invite_link_label": "Gonbidapen esteka [needs review]", + "onboarding_invite_link_placeholder": "Itsatsi hemen gonbidapen esteka… [needs review]", + "onboarding_create_btn": "Sortu kolektiboa [needs review]", + "onboarding_join_btn": "Elkartu kolektiboarekin [needs review]", + "collective_name_label": "Kolektiboaren izena [needs review]", + "collective_name_placeholder": "adib. Gure Etxea [needs review]", + "invitation_title": "Gonbidapen bat duzu [needs review]", + "invitation_subtitle": "Kolektibo batera elkartzeko gonbidatu zaituzte. [needs review]", + "invitation_accept_btn": "Gonbidapena onartu [needs review]", + "invitation_success": "Kolektiboari elkartu zara! [needs review]", + "invitation_invalid_link": "Honek ez dirudi baliozko gonbidapen esteka. [needs review]", + "invitation_error_not_found": "Gonbidapen esteka hau ez da baliozkoa. [needs review]", + "invitation_error_already_used": "Gonbidapen hau dagoeneko erabili da. [needs review]", + "invitation_error_expired": "Gonbidapen hau iraungita dago. [needs review]", + "invitation_error_already_member": "Dagoeneko kolektibo honetako kide zara. [needs review]", + "invitation_error_unauthenticated": "Gonbidapena onartzeko, saioa hasi behar duzu. [needs review]", + "invitation_error_unknown": "Zerbait gaizki joan da. Saiatu berriro, mesedez. [needs review]", + "manage_title": "Kolektiboa kudeatu [needs review]", + "manage_members": "Kideak [needs review]", + "manage_you": "zu [needs review]", + "manage_remove": "Kidea ezabatu [needs review]", + "manage_invite_title": "Inor gonbidatu [needs review]", + "manage_invite_role": "Rola [needs review]", + "manage_generate_link": "Gonbidapen esteka sortu [needs review]", + "manage_copy": "Kopiatu [needs review]", + "manage_copied": "Kopiatuta! [needs review]", + "manage_link_expires": "Esteka 7 egunetan iraungiko da. [needs review]", + "role_admin": "Administratzailea [needs review]", + "role_member": "Kidea [needs review]", + "role_guest": "Gonbidatua [needs review]", + "settings_title": "Ezarpenak [needs review]", + "settings_display_name": "Ikusgai dagoen izena [needs review]", + "settings_saving": "Gordetzen… [needs review]", + "settings_language": "Hizkuntza [needs review]", + "settings_avatar": "Abatarra [needs review]", + "settings_avatar_initials": "Inizialak [needs review]", + "settings_avatar_emoji": "Emoji [needs review]", + "settings_avatar_upload": "Argazkia [needs review]", + "settings_avatar_upload_btn": "Argazkia igo… [needs review]", + "settings_account": "Kontua [needs review]", + "settings_keycloak_link": "Helbide elektronikoa edo pasahitza aldatu [needs review]", + "settings_logout": "Saioa itxi [needs review]", + "settings_save": "Aldaketak gorde [needs review]", + "settings_saved": "Aldaketak gordeta [needs review]", + "invite_member": "Kidea gonbidatu [needs review]", + "invite_email_label": "Helbide elektronikoa [needs review]", + "invite_send": "Gonbidapena bidali [needs review]", + "masthead_lists_label": "Espazioa [needs review]", + "masthead_tasks_label": "Zereginak [needs review]", + "masthead_notes_label": "Biltegia [needs review]", + "masthead_search_label": "Bilatu [needs review]", + "masthead_search_title": "Aurkitu dena. [needs review]", + "lists_title": "Erosketa zerrendak [needs review]", + "lists_new_list": "Zerrenda berria [needs review]", + "lists_no_lists": "Zerrendarik ez [needs review]", + "lists_create_first": "Sortu zure lehen erosketa zerrenda [needs review]", + "lists_trash": "Zakarrontzia [needs review]", + "list_status_active": "Zerrenda aktiboa [needs review]", + "list_status_completed": "Osatua [needs review]", + "list_status_archived": "Artxibatua [needs review]", + "list_name_placeholder": "adib. Asteko erosketa [needs review]", + "list_items_empty": "Zerrenda hutsa [needs review]", + "list_items_empty_hint": "Gehitu zure lehen produktua behean [needs review]", + "list_reset": "Zerrenda berrabiarazi [needs review]", + "list_archive": "Artxibatu [needs review]", + "list_delete": "Zakarrontzira eraman [needs review]", + "list_actions": "Zerrendaren ekintzak [needs review]", + "list_item_placeholder": "Produktua gehitu… [needs review]", + "list_qty_label": "Kop. [needs review]", + "list_add_item": "Produktua gehitu… [needs review]", + "list_to_buy": "Erosteko [needs review]", + "list_checked": "Markatuta [needs review]", + "list_start_session": "Erosi [needs review]", + "list_finish_shopping": "Erosketa amaitu [needs review]", + "list_finish_confirm": "Zerrenda hau osatutzat eman? [needs review]", + "list_finish_confirm_yes": "Bai, amaitu [needs review]", + "list_finish_confirm_no": "Erosten jarraitu [needs review]", + "tasks_title": "Zereginak [needs review]", + "tasks_new_list": "Zerrenda berria [needs review]", + "tasks_no_lists": "Zeregin zerrendarik ez [needs review]", + "tasks_create_first": "Sortu zure lehen zeregin zerrenda [needs review]", + "tasks_new_list_placeholder": "Zeregin zerrenda berria [needs review]", + "tasks_new_task_placeholder": "Zeregina gehitu [needs review]", + "tasks_empty": "Zereginik ez [needs review]", + "tasks_empty_hint": "Gehitu zure lehen zeregina behean [needs review]", + "tasks_completed_section": "Osatuak [needs review]", + "tasks_pending_section": "Egiteke [needs review]", + "tasks_completed_by": "{name}-(e)k osatua [needs review]", + "tasks_toggle": "Zeregina txandakatu [needs review]", + "tasks_uncheck": "Zeregina desmarkatu [needs review]", + "tasks_delete": "Zeregina ezabatu [needs review]", + "tasks_delete_list": "Zerrenda ezabatu [needs review]", + "tasks_delete_list_confirm": "Zerrenda hau eta bere zeregin guztiak ezabatu? [needs review]", + "notes_title": "Oharrak [needs review]", + "notes_new": "Ohar berria [needs review]", + "notes_pinned_section": "Finkatuak [needs review]", + "notes_active_section": "Oharrak [needs review]", + "notes_archive": "Artxibatu [needs review]", + "notes_archive_view": "Artxibatutako oharrak [needs review]", + "notes_archived_empty": "Artxibatutako oharrik ez [needs review]", + "notes_trash_view": "Oharren zakarrontzia [needs review]", + "notes_trash_empty": "Zakarrontzia hutsik dago [needs review]", + "notes_pin": "Oharra finkatu [needs review]", + "notes_unpin": "Oharra desfinkatu [needs review]", + "notes_unarchive": "Oharra desartxibatu [needs review]", + "notes_send_to_trash": "Zakarrontzira bidali [needs review]", + "notes_restore": "Berreskuratu [needs review]", + "notes_duplicate": "Bikoiztu [needs review]", + "notes_color": "Kolorea [needs review]", + "notes_title_placeholder": "Izenburua [needs review]", + "notes_content_placeholder": "Hasi idazten… [needs review]", + "notes_content_aria": "Edukia [needs review]", + "notes_back_to_board": "Taulara itzuli [needs review]", + "notes_saved": "Gordeta [needs review]", + "notes_saving": "Gordetzen… [needs review]", + "notes_empty_board": "Oharrik ez [needs review]", + "notes_empty_board_hint": "Sakatu \"Ohar berria\" hasteko [needs review]", + "search_title": "Bilatu [needs review]", + "search_placeholder": "Bilatu zerrendak, zereginak, oharrak… [needs review]", + "search_filter_all": "Dena [needs review]", + "search_filter_lists": "Zerrendak [needs review]", + "search_filter_tasks": "Zereginak [needs review]", + "search_filter_notes": "Oharrak [needs review]", + "search_group_shopping_items": "Produktuak [needs review]", + "search_group_tasks": "Zereginak [needs review]", + "search_group_notes": "Oharrak [needs review]", + "search_empty": "Emaitzarik ez [needs review]", + "search_hint": "Idatzi gutxienez bi karaktere [needs review]", + "trash_restore": "Berreskuratu [needs review]", + "trash_delete_permanent": "Behin betiko ezabatu [needs review]", + "trash_empty": "Zakarrontzia hutsik dago [needs review]", + "trash_expires_in": "{days} egunetan iraungiko da [needs review]", + "confirm_delete": "Ezabatu? [needs review]", + "cancel": "Utzi [needs review]", + "save": "Gorde [needs review]", + "action_delete": "Ezabatu [needs review]", + "edit": "Editatu [needs review]", + "add": "Gehitu [needs review]", + "done": "Eginda [needs review]", + "pwa_update_available": "Bertsio berria eskuragarri [needs review]", + "pwa_update_reload": "Birkargatu [needs review]", + "pwa_offline_ready": "Lineaz kanpo erabiltzeko prest [needs review]", + "pwa_offline_chip": "Konexiorik gabe [needs review]", + "pwa_offline_tooltip": "Konexiorik gabe — aldaketak itzultzean sinkronizatuko dira [needs review]", + "pwa_pending_ops": "{count} aldaketa zain [needs review]", + "pwa_pending_ops_plural": "{count} aldaketa zain [needs review]", + "pwa_sync_conflicts_banner": "Sinkronizazio gatazkak daude [needs review]", + "pwa_sync_conflicts_review": "Berrikusi [needs review]", + "sync_conflicts_title": "Sinkronizazio gatazkak [needs review]", + "sync_conflicts_empty": "Ezer zain — lokala eta zerbitzaria bat datoz. [needs review]", + "sync_conflicts_discard_local": "Lokala baztertu [needs review]", + "sync_conflicts_discard_remote": "Urrunekoa baztertu [needs review]", + "sync_conflicts_back": "Ezarpenetara itzuli [needs review]", + "settings_about": "Honi buruz [needs review]", + "settings_about_version": "v{version} · {commit} · {date} [needs review]", + "settings_about_view_changelog": "Historia ikusi [needs review]", + "changelog_modal_title": "Aldaketen historia [needs review]", + "changelog_modal_close": "Itxi [needs review]", + "sync_offline": "Konexiorik gabe — aldaketak berriz konektatzean sinkronizatuko dira [needs review]", + "sync_syncing": "Sinkronizatzen… [needs review]", + "undo": "Desegin [needs review]", + "undo_deleted_item": "{name} ezabatuta [needs review]", + "list_item_delete_aria": "Produktua ezabatu [needs review]", + "list_qty_decrease": "Kopurua jaitsi [needs review]", + "list_qty_increase": "Kopurua igo [needs review]", + "list_reorder_handle": "Berrantolatu [needs review]", + "list_edit_item": "Produktua editatu [needs review]", + "list_confirm": "Baieztatu [needs review]", + "list_close": "Itxi [needs review]", + "list_select": "Hautatu [needs review]", + "list_selection_count": "{n} hautatuta [needs review]", + "list_cancel_selection": "Utzi [needs review]", + "list_bulk_delete": "Ezabatu [needs review]", + "list_bulk_move": "Mugitu [needs review]", + "list_bulk_mark_checked": "Markatu [needs review]", + "list_undo_bulk_delete": "{n} ezabatuta [needs review]", + "list_move_title": "Zerrendara eraman [needs review]", + "list_move_create_new": "Zerrenda berria sortu [needs review]", + "settings_appearance": "Itxura [needs review]", + "settings_theme_light": "Argia [needs review]", + "settings_theme_dark": "Iluna [needs review]", + "settings_theme_system": "Sistema [needs review]", + "settings_sync_conflicts": "Sinkronizazio gatazkak [needs review]", + "settings_sync_conflicts_empty": "Erregistratutako gatazkarik ez. [needs review]", + "settings_sync_conflicts_export": "JSON esportatu [needs review]", + "settings_collectives": "Zure kolektiboak [needs review]", + "settings_collectives_empty": "Ez zara inongo kolektiboko kide. [needs review]", + "settings_collectives_leave": "Utzi [needs review]", + "settings_collectives_confirm_leave_title": "{name} utzi? [needs review]", + "settings_collectives_confirm_leave_body": "Kolektibo honetarako sarbidea galduko duzu. Edukia gainerako kideentzat geratuko da. [needs review]", + "settings_collectives_confirm_leave_button": "Kolektiboa utzi [needs review]", + "settings_collectives_dissolve_hint": "Kide bakarra zara — erabili 'Desegin' kolektiboaren orrian horren ordez. [needs review]", + "settings_danger_zone": "Eremu arriskutsua [needs review]", + "settings_delete_account": "Nire kontua ezabatu [needs review]", + "settings_delete_account_blurb": "Behin betiko ezabatzen ditu zure kontua eta zure kidetza guztiak. Sortu duzun edukia kolektiboetan geratuko da, ezabatutako erabiltzaile bati esleitua. [needs review]", + "settings_delete_account_modal_title": "Zure kontua ezabatu? [needs review]", + "settings_delete_account_modal_body": "Hau behin betikoa da. Ezabatzen dena: zure kontua, zure kidetzak, zure hizkuntza eta gai gordeak. Geratzen dena: sortu dituzun zerrendak, zereginak edo oharrak — euren kolektiboetan geratzen dira, ezabatutako erabiltzaile bati esleituak. Kolektiboren bateko administratzaile bakarra bazara, kide zaharrena automatikoki sustatuko da. Oharra: identitate-hornitzailearen kontua EZ da ezabatzen; helbide elektroniko berarekin berriro erregistratu zaitezke eta profil berri bat sortuko da. [needs review]", + "settings_delete_account_confirm_label": "Idatzi {word} baieztatzeko [needs review]", + "settings_delete_account_confirm_word": "EZABATU [needs review]", + "settings_delete_account_button": "Kontua behin betiko ezabatu [needs review]", + "settings_delete_account_blocked": "Sustatu ezin diren kideak dituen kolektibo bateko administratzaile bakarra zara. Sustatu norbait lehenik edo ezabatu gainerako kideak. [needs review]", + "common_cancel": "Utzi [needs review]", + "manage_collective_name_label": "Kolektiboaren izena [needs review]", + "manage_collective_emoji_label": "Ikonoa [needs review]", + "manage_collective_save": "Gorde [needs review]", + "manage_dissolve_section": "Eremu arriskutsua [needs review]", + "manage_dissolve_button": "Kolektiboa desegin [needs review]", + "manage_dissolve_blurb": "Kolektibo hau eta bere eduki guztia behin betiko ezabatzen ditu. Ezin da desegin. [needs review]", + "manage_dissolve_title": "{name} desegin? [needs review]", + "manage_dissolve_warning": "{lists} zerrenda, {tasks} zeregin eta {notes} ohar ezabatuko dira. Ekintza hau itzulezina da. [needs review]", + "manage_dissolve_confirm_label": "Idatzi {name} baieztatzeko [needs review]", + "manage_dissolve_confirm_button": "Behin betiko desegin [needs review]", + "sidebar_create_collective": "+ Kolektibo berria [needs review]", + "create_collective_modal_title": "Kolektibo berria [needs review]", + "create_collective_modal_button": "Sortu [needs review]", + "tag_picker_placeholder": "Etiketa bilatu edo sortu [needs review]", + "tag_picker_create": "«{name}» sortu [needs review]", + "tag_picker_empty": "Oraindik ez dago etiketarik. [needs review]", + "tag_picker_label": "Etiketak [needs review]", + "list_filter_by_tag": "Etiketaz iragazi [needs review]", + "list_filter_clear": "Garbitu [needs review]", + "settings_tags_section": "Etiketak [needs review]", + "settings_tags_empty": "Oraindik ez dago etiketarik — sortu edozein zerrendatatik. [needs review]", + "settings_tags_delete": "Ezabatu [needs review]", + "settings_tags_color": "Kolorea [needs review]", + "section_disabled_for_collective": "Atal hau kolektibo honentzat ezkutatuta dago. [needs review]", + "settings_section_visibility_title": "Ikusgai dauden atalak [needs review]", + "settings_section_visibility_blurb": "Ezkutatu erabiltzen ez dituzun goi-mailako atalak. Zure administratzaileak ere kolektibo osorako ezkuta ditzake. [needs review]", + "settings_section_visibility_overridden": "Kolektiboak ezkutatua. [needs review]", + "collective_section_visibility_title": "Kolektiboaren atalak [needs review]", + "collective_section_visibility_blurb": "Ezkutatu atalak kolektibo honetako kide guztientzat. Kide bakoitzak ere bere buruarentzat ezkuta ditzake. [needs review]", + "section_label_lists": "Zerrendak [needs review]", + "section_label_tasks": "Zereginak [needs review]", + "section_label_notes": "Oharrak [needs review]", + "section_label_search": "Bilatu [needs review]", + "admin_banner": "Admin modua — ekintzak erregistratzen dira [needs review]", + "admin_nav_collectives": "Kolektiboak [needs review]", + "admin_nav_admins": "Administratzaileak [needs review]", + "admin_nav_audit": "Erregistroa [needs review]", + "admin_nav_server": "Zerbitzaria [needs review]", + "admin_menu_entry": "Zerbitzariko administratzailea [needs review]", + "admin_collectives_title": "Kolektiboak [needs review]", + "admin_collectives_search_placeholder": "Izenez bilatu… [needs review]", + "admin_collectives_empty": "Ez dator bat kolektiborik. [needs review]", + "admin_collectives_col_name": "Izena [needs review]", + "admin_collectives_col_members": "Kideak [needs review]", + "admin_collectives_col_created": "Sortua [needs review]", + "admin_collectives_col_status": "Egoera [needs review]", + "admin_collectives_status_active": "Aktiboa [needs review]", + "admin_collectives_status_deleted": "Ezabatua [needs review]", + "admin_action_view": "Ikusi [needs review]", + "admin_action_soft_delete": "Soft-delete [needs review]", + "admin_action_restore": "Berreskuratu [needs review]", + "admin_action_hard_delete": "Ezabatu [needs review]", + "admin_action_remove": "Kanporatu [needs review]", + "admin_modal_soft_delete_title": "Kolektiboa soft-delete egin [needs review]", + "admin_modal_soft_delete_help": "Kolektiboa ezkutatuta geratzen da kideentzat, baina berreskuragarri 30 egunez. Arrazoia erregistratzen da. [needs review]", + "admin_modal_reason_label": "Arrazoia [needs review]", + "admin_modal_reason_placeholder": "Zergatik? [needs review]", + "admin_modal_hard_delete_title": "Kolektiboa ezabatu [needs review]", + "admin_modal_hard_delete_help": "Hau itzulezina da. Zerrenda, item, zeregin eta ohar guztiak ezabatuko dira. [needs review]", + "admin_modal_hard_delete_confirm": "Itzulezina dela ulertzen dut [needs review]", + "admin_modal_hard_delete_force_label": "Behartu (30 eguneko itxaronaldia saltatu) [needs review]", + "admin_modal_cancel": "Utzi [needs review]", + "admin_modal_confirm": "Baieztatu [needs review]", + "admin_collective_detail_back": "Kolektiboetara itzuli [needs review]", + "admin_collective_detail_members": "Kideak [needs review]", + "admin_collective_detail_recent_actions": "Azken ekintzak [needs review]", + "admin_modal_remove_member_title": "Kidea kanporatu [needs review]", + "admin_modal_remove_member_help": "Kidetza berehala ezeztatzen da. Arrazoia erregistratzen da. [needs review]", + "admin_admins_title": "Zerbitzariko administratzaileak [needs review]", + "admin_admins_promote": "Erabiltzailea sustatu [needs review]", + "admin_admins_promote_help": "Sartu sustatu beharreko erabiltzailearen helbide elektronikoa. [needs review]", + "admin_admins_email_label": "Helbide elektronikoa [needs review]", + "admin_admins_email_placeholder": "erabiltzailea@adibidea.com [needs review]", + "admin_admins_email_not_found": "Helbide elektroniko hori duen erabiltzailerik ez. [needs review]", + "admin_admins_revoke_self": "zu (ezin duzu zeure burua ezeztatu administratzaile bakarra bazara) [needs review]", + "admin_admins_revoke": "Ezeztatu [needs review]", + "admin_audit_title": "Ekintzen erregistroa [needs review]", + "admin_audit_empty": "Oraindik ez da ekintzarik erregistratu. [needs review]", + "admin_audit_col_when": "Noiz [needs review]", + "admin_audit_col_actor": "Eragilea [needs review]", + "admin_audit_col_action": "Ekintza [needs review]", + "admin_audit_col_target": "Helburua [needs review]", + "admin_audit_col_payload": "Payload [needs review]", + "admin_server_title": "Zerbitzariaren konfigurazioa [needs review]", + "admin_server_default_sections": "Atalen lehenetsitako ikusgaitasuna [needs review]", + "admin_server_default_sections_help": "Zerbitzari mailako override-a. Hemen ON edo OFF jartzeak edozein kolektibo edo erabiltzaile gainditzen du. [needs review]", + "admin_server_section_state_off": "OFF [needs review]", + "admin_server_section_state_on": "ON [needs review]", + "admin_server_section_state_unset": "Iritzirik gabe [needs review]", + "admin_server_info": "Zerbitzariaren informazioa [needs review]", + "admin_error_forbidden": "Ez duzu eremu honetarako sarbiderik. [needs review]", + "admin_error_unknown": "Zerbait gaizki joan da. Saiatu berriro. [needs review]", + "common_items_title": "Item ohikoak [needs review]", + "common_items_blurb": "Sustatu beti iradokizunetan lehenik agertzea nahi dituzun itemak, edo ezkutatu ikusi nahi ez dituzunak. Kideek katalogoa ikusten dute baina ezin dute editatu. [needs review]", + "common_items_search_placeholder": "Itemak bilatu… [needs review]", + "common_items_empty": "Oraindik ez dago item ohikorik. Zerrendetara gehitzen diren itemak hemen agertuko dira. [needs review]", + "common_items_empty_filtered": "Ez dator bat itemik zure bilaketarekin. [needs review]", + "common_items_col_name": "Izena [needs review]", + "common_items_col_weight": "Ikusgaitasuna [needs review]", + "common_items_col_uses": "Erabilerak [needs review]", + "common_items_col_last_used": "Azken erabilera [needs review]", + "common_items_col_actions": "Ekintzak [needs review]", + "common_items_state_hide": "Ezkutatu [needs review]", + "common_items_state_normal": "Normala [needs review]", + "common_items_state_boost": "Nabarmendu [needs review]", + "common_items_state_locked_tooltip": "Administratzaileek soilik kudea ditzakete item ohikoak. [needs review]", + "common_items_purge_button": "Ezabatu [needs review]", + "common_items_purge_modal_title": "{name} ezabatu? [needs review]", + "common_items_purge_modal_body": "Honek iradokizunetatik sarrera kentzen du soilik. Dagoeneko zerrendetan dauden itemak ez dira aldatzen. [needs review]", + "common_items_purge_confirm": "Ezabatu [needs review]", + "common_items_add_button": "Item komuna gehitu [needs review]", + "common_items_add_modal_title": "Item komuna gehitu [needs review]", + "common_items_add_name_label": "Itemaren izena [needs review]", + "common_items_add_name_placeholder": "adib. oliba olioa [needs review]", + "common_items_add_visibility_label": "Ikusgaitasuna [needs review]", + "common_items_add_save": "Gehitu [needs review]", + "create_list_modal_title": "Erosketa zerrenda berria [needs review]", + "create_list_input_label": "Zerrendaren izena [needs review]", + "create_list_input_placeholder": "adib. Asteko erosketa [needs review]", + "create_list_submit": "Sortu [needs review]", + "create_list_suggestion_chip": "Iradokizuna: {title} [needs review]", + "create_list_auto_numbered_toast": "«{title}» bezala sortua [needs review]", + "manage_list_titles_section_title": "Iradokitako izenburuak [needs review]", + "manage_list_titles_blurb": "Aurrekonfiguratu zerrenda berrientzat lehenetsitako izenburu bat eta mantendu iradokizun zerrenda bat. [needs review]", + "manage_default_list_title_label": "Lehenetsitako izenburua [needs review]", + "manage_default_list_title_placeholder": "adib. Asteko erosketa [needs review]", + "manage_default_list_title_clear": "Garbitu [needs review]", + "manage_list_titles_empty": "Oraindik ez dago iradokitako izenbururik. [needs review]", + "manage_list_titles_add_button": "Izenburua gehitu [needs review]", + "manage_list_titles_add_modal_title": "Iradokitako izenburua gehitu [needs review]", + "manage_list_titles_add_name_label": "Izenburua [needs review]", + "manage_list_titles_add_name_placeholder": "adib. Erosketa [needs review]", + "manage_list_titles_add_save": "Gehitu [needs review]", + "manage_list_titles_remove": "Ezabatu [needs review]", + "manage_list_titles_readonly": "Administratzaileek soilik kudea ditzakete izenburu hauek. [needs review]" +} diff --git a/apps/web/project.inlang/settings.json b/apps/web/project.inlang/settings.json index d8a354b..317668c 100644 --- a/apps/web/project.inlang/settings.json +++ b/apps/web/project.inlang/settings.json @@ -1,7 +1,7 @@ { "$schema": "https://inlang.com/schema/project-settings", "sourceLanguageTag": "en", - "languageTags": ["en", "es"], + "languageTags": ["en", "es", "eu"], "modules": [ "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@1/dist/index.js" diff --git a/scripts/check-locales.mjs b/scripts/check-locales.mjs new file mode 100644 index 0000000..4420c69 --- /dev/null +++ b/scripts/check-locales.mjs @@ -0,0 +1,90 @@ +#!/usr/bin/env node +/** + * Locale key parity check (Fase 20.2.2). + * + * Compares the JSON message bundles under apps/web/messages/ against the + * source-of-truth bundle (`en.json`). Fails (exit 1) if any of the + * non-source locales has missing or extra keys. + * + * The `$schema` property is intentionally ignored — it's a JSON-Schema + * pointer, not a translatable message. + * + * Usage: + * node scripts/check-locales.mjs # check all + * node scripts/check-locales.mjs eu # check only eu + * + * Run via the Justfile recipe `check-locales` or `pnpm -F @colectivo/web …` + * — kept dependency-free on purpose so it works at any tooling level. + */ +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const MESSAGES_DIR = join(__dirname, '..', 'apps', 'web', 'messages'); +const SOURCE_LOCALE = 'en'; +const IGNORED_KEYS = new Set(['$schema']); + +function loadLocale(name) { + const path = join(MESSAGES_DIR, `${name}.json`); + const raw = readFileSync(path, 'utf8'); + const obj = JSON.parse(raw); + const keys = new Set(Object.keys(obj).filter((k) => !IGNORED_KEYS.has(k))); + return { name, path, keys, obj }; +} + +function diffSets(reference, candidate) { + const missing = []; + const extra = []; + for (const k of reference) { + if (!candidate.has(k)) missing.push(k); + } + for (const k of candidate) { + if (!reference.has(k)) extra.push(k); + } + return { missing, extra }; +} + +function main() { + const filter = process.argv.slice(2); + const allFiles = readdirSync(MESSAGES_DIR).filter((f) => f.endsWith('.json')); + const allLocales = allFiles.map((f) => f.replace(/\.json$/, '')); + + if (!allLocales.includes(SOURCE_LOCALE)) { + console.error(`Source locale ${SOURCE_LOCALE}.json not found in ${MESSAGES_DIR}`); + process.exit(2); + } + + const source = loadLocale(SOURCE_LOCALE); + const targets = allLocales + .filter((l) => l !== SOURCE_LOCALE) + .filter((l) => filter.length === 0 || filter.includes(l)) + .map(loadLocale); + + let failed = false; + for (const t of targets) { + const { missing, extra } = diffSets(source.keys, t.keys); + if (missing.length === 0 && extra.length === 0) { + console.log(`✓ ${t.name}: ${t.keys.size} keys match ${SOURCE_LOCALE}`); + continue; + } + failed = true; + console.error(`✗ ${t.name}: key drift vs ${SOURCE_LOCALE}`); + if (missing.length > 0) { + console.error(` missing (${missing.length}):`); + for (const k of missing) console.error(` - ${k}`); + } + if (extra.length > 0) { + console.error(` extra (${extra.length}):`); + for (const k of extra) console.error(` + ${k}`); + } + } + + if (failed) { + process.exit(1); + } +} + +main();