feat(locale): add extras.po overlay merged on top of distributed .po
Frappe's gettext extractor misses two categories of translatable strings: bare literals in source code that get wrapped in __() only at render time (e.g. "Save Current Filter" in list_filter.js:88), and labels stored as data in DocType fixtures (e.g. Number Card / Dashboard Chart labels like "Open Opportunity", "Incoming Leads"). These never make it into upstream .pot files, so they cannot be translated via the standard cycle. Adding such msgids directly to the main <lang>.po works but is fragile: the next bench update-po-files run marks them obsolete because the .pot doesn't contain them. This change introduces extras.po sibling files in each translations_<lang>/ <app>/ directory. setup_locale() copies the main <lang>.po to the target app's locale dir as before, then overlays extras.po on top via babel's read_po/write_po. Obsolete (#~) entries are promoted back to active when a matching extras msgid exists. Extras wins on conflict. Seeded extras files cover currently-known gaps in frappe (Save Current Filter) and erpnext (Open Opportunity plus 30 other CRM/dashboard labels). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b6da5b1bc0
commit
ba1f4c3ee0
|
|
@ -9,6 +9,7 @@ def setup_locale():
|
||||||
locale_dir = os.path.join(bench_path, "apps", "az_locale", "az_locale", "locale")
|
locale_dir = os.path.join(bench_path, "apps", "az_locale", "az_locale", "locale")
|
||||||
|
|
||||||
copied = 0
|
copied = 0
|
||||||
|
merged = 0
|
||||||
skipped = 0
|
skipped = 0
|
||||||
|
|
||||||
for entry in sorted(os.listdir(locale_dir)):
|
for entry in sorted(os.listdir(locale_dir)):
|
||||||
|
|
@ -43,5 +44,60 @@ def setup_locale():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[az_locale] Error copying to {dest_file}: {e}")
|
print(f"[az_locale] Error copying to {dest_file}: {e}")
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
print(f"[az_locale] Locale setup complete: {copied} file(s) copied, {skipped} skipped")
|
extras_file = os.path.join(app_dir, "extras.po")
|
||||||
|
if os.path.exists(extras_file):
|
||||||
|
added = _merge_extras(dest_file, extras_file)
|
||||||
|
if added is not None:
|
||||||
|
print(f"[az_locale] Merged {added} extras into {app_name}/{lang}.po")
|
||||||
|
merged += added
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"[az_locale] Locale setup complete: {copied} file(s) copied, "
|
||||||
|
f"{merged} extras merged, {skipped} skipped"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_extras(dest_file, extras_file):
|
||||||
|
"""Overlay msgid/msgstr pairs from `extras_file` onto `dest_file`.
|
||||||
|
|
||||||
|
Extras wins on conflict and revives any matching obsolete entries
|
||||||
|
(those marked `#~` by msgmerge after upstream .pot regeneration).
|
||||||
|
Returns the count of merged messages, or None on error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from babel.messages.pofile import read_po, write_po
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"[az_locale] Error: babel.messages not available ({e}), skipping extras merge")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(dest_file, "rb") as f:
|
||||||
|
dest = read_po(f)
|
||||||
|
with open(extras_file, "rb") as f:
|
||||||
|
extras = read_po(f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[az_locale] Error reading PO files for merge ({dest_file}): {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for msg in extras:
|
||||||
|
if not msg.id:
|
||||||
|
continue
|
||||||
|
# Resurrect from obsolete section if upstream pot dropped this msgid.
|
||||||
|
try:
|
||||||
|
dest.obsolete.pop(msg.id, None)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
dest[msg.id] = msg
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(dest_file, "wb") as f:
|
||||||
|
write_po(f, dest, sort_output=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[az_locale] Error writing merged PO file {dest_file}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: az_locale extras\n"
|
||||||
|
"Language: az\n"
|
||||||
|
"Language-Team: Azerbaijani\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. Number Card / Dashboard Chart / Dashboard fixture labels — data, not source code,
|
||||||
|
#. so not extracted into upstream .pot. Rendered via __() in
|
||||||
|
#. frappe/public/js/frappe/widgets/base_widget.js:102.
|
||||||
|
|
||||||
|
# === Number Cards ===
|
||||||
|
msgid "Open Opportunity"
|
||||||
|
msgstr "Açıq Fürsət"
|
||||||
|
|
||||||
|
msgid "Active Customers"
|
||||||
|
msgstr "Aktiv Müştərilər"
|
||||||
|
|
||||||
|
msgid "Active Suppliers"
|
||||||
|
msgstr "Aktiv Təchizatçılar"
|
||||||
|
|
||||||
|
msgid "Annual Purchase"
|
||||||
|
msgstr "İllik Satınalma"
|
||||||
|
|
||||||
|
msgid "Annual Sales"
|
||||||
|
msgstr "İllik Satış"
|
||||||
|
|
||||||
|
msgid "Average Sales Order Value"
|
||||||
|
msgstr "Orta Satış Sifarişi Dəyəri"
|
||||||
|
|
||||||
|
msgid "Sales Orders Count"
|
||||||
|
msgstr "Satış Sifarişlərinin Sayı"
|
||||||
|
|
||||||
|
msgid "Timesheet Working Hours"
|
||||||
|
msgstr "İş Saatları Cədvəli"
|
||||||
|
|
||||||
|
msgid "Total Incoming Bills"
|
||||||
|
msgstr "Cəmi Daxil Olan Hesablar"
|
||||||
|
|
||||||
|
msgid "Total Incoming Payment"
|
||||||
|
msgstr "Cəmi Daxil Olan Ödəniş"
|
||||||
|
|
||||||
|
msgid "Total Outgoing Bills"
|
||||||
|
msgstr "Cəmi Çıxan Hesablar"
|
||||||
|
|
||||||
|
msgid "Total Outgoing Payment"
|
||||||
|
msgstr "Cəmi Çıxan Ödəniş"
|
||||||
|
|
||||||
|
# === Dashboard Charts ===
|
||||||
|
msgid "Accounts Payable Ageing"
|
||||||
|
msgstr "Ödəniləcək Hesabların Yaşlanması"
|
||||||
|
|
||||||
|
msgid "Accounts Receivable Ageing"
|
||||||
|
msgstr "Alınacaq Hesabların Yaşlanması"
|
||||||
|
|
||||||
|
msgid "Delivery Trends"
|
||||||
|
msgstr "Çatdırılma Trendləri"
|
||||||
|
|
||||||
|
msgid "Incoming Bills (Purchase Invoice)"
|
||||||
|
msgstr "Daxil Olan Hesablar (Satınalma Fakturası)"
|
||||||
|
|
||||||
|
msgid "Incoming Leads"
|
||||||
|
msgstr "Daxil Olan Potensiyal Müştərilər"
|
||||||
|
|
||||||
|
msgid "Item Shortage Summary"
|
||||||
|
msgstr "Element Çatışmazlığı Xülasəsi"
|
||||||
|
|
||||||
|
msgid "Item-wise Annual Sales"
|
||||||
|
msgstr "Elementlər üzrə İllik Satış"
|
||||||
|
|
||||||
|
msgid "Location-wise Asset Value"
|
||||||
|
msgstr "Ərazi üzrə Aktiv Dəyəri"
|
||||||
|
|
||||||
|
msgid "Material Request Analysis"
|
||||||
|
msgstr "Material Sorğusu Təhlili"
|
||||||
|
|
||||||
|
msgid "Oldest Items"
|
||||||
|
msgstr "Ən Köhnə Elementlər"
|
||||||
|
|
||||||
|
msgid "Opportunities via Campaigns"
|
||||||
|
msgstr "Kampaniyalar üzrə Fürsətlər"
|
||||||
|
|
||||||
|
msgid "Opportunity Trends"
|
||||||
|
msgstr "Fürsət Trendləri"
|
||||||
|
|
||||||
|
msgid "Outgoing Bills (Sales Invoice)"
|
||||||
|
msgstr "Çıxan Hesablar (Satış Fakturası)"
|
||||||
|
|
||||||
|
msgid "Territory Wise Opportunity Count"
|
||||||
|
msgstr "Ərazi üzrə Fürsətlərin Sayı"
|
||||||
|
|
||||||
|
msgid "Territory Wise Sales"
|
||||||
|
msgstr "Ərazi üzrə Satış"
|
||||||
|
|
||||||
|
msgid "Top Customers"
|
||||||
|
msgstr "Ən Yaxşı Müştərilər"
|
||||||
|
|
||||||
|
msgid "Top Suppliers"
|
||||||
|
msgstr "Ən Yaxşı Təchizatçılar"
|
||||||
|
|
||||||
|
msgid "Warehouse wise Stock Value"
|
||||||
|
msgstr "Anbar üzrə Ehtiyat Dəyəri"
|
||||||
|
|
||||||
|
# === Dashboard parents ===
|
||||||
|
msgid "Project"
|
||||||
|
msgstr "Layihə"
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: az_locale extras\n"
|
||||||
|
"Language: az\n"
|
||||||
|
"Language-Team: Azerbaijani\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#. Hardcoded literal at frappe/public/js/frappe/list/list_filter.js:88
|
||||||
|
#. wrapped by __() only at render time (line 162), so not picked up by
|
||||||
|
#. bench generate-pot-file. Provide translation here so __() finds it.
|
||||||
|
msgid "Save Current Filter"
|
||||||
|
msgstr "Cari filtri yadda saxla"
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: az_locale extras\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"Language-Team: Russian\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#. Number Card / Dashboard Chart / Dashboard fixture labels — data, not source code,
|
||||||
|
#. so not extracted into upstream .pot. Rendered via __() in
|
||||||
|
#. frappe/public/js/frappe/widgets/base_widget.js:102.
|
||||||
|
|
||||||
|
# === Number Cards ===
|
||||||
|
msgid "Open Opportunity"
|
||||||
|
msgstr "Открытые возможности"
|
||||||
|
|
||||||
|
msgid "Active Customers"
|
||||||
|
msgstr "Активные клиенты"
|
||||||
|
|
||||||
|
msgid "Active Suppliers"
|
||||||
|
msgstr "Активные поставщики"
|
||||||
|
|
||||||
|
msgid "Annual Purchase"
|
||||||
|
msgstr "Годовые закупки"
|
||||||
|
|
||||||
|
msgid "Annual Sales"
|
||||||
|
msgstr "Годовые продажи"
|
||||||
|
|
||||||
|
msgid "Average Sales Order Value"
|
||||||
|
msgstr "Средняя сумма заказа на продажу"
|
||||||
|
|
||||||
|
msgid "Sales Orders Count"
|
||||||
|
msgstr "Количество заказов на продажу"
|
||||||
|
|
||||||
|
msgid "Timesheet Working Hours"
|
||||||
|
msgstr "Рабочие часы по табелю"
|
||||||
|
|
||||||
|
msgid "Total Incoming Bills"
|
||||||
|
msgstr "Всего входящих счетов"
|
||||||
|
|
||||||
|
msgid "Total Incoming Payment"
|
||||||
|
msgstr "Всего входящих платежей"
|
||||||
|
|
||||||
|
msgid "Total Outgoing Bills"
|
||||||
|
msgstr "Всего исходящих счетов"
|
||||||
|
|
||||||
|
msgid "Total Outgoing Payment"
|
||||||
|
msgstr "Всего исходящих платежей"
|
||||||
|
|
||||||
|
# === Dashboard Charts ===
|
||||||
|
msgid "Accounts Payable Ageing"
|
||||||
|
msgstr "Старение кредиторской задолженности"
|
||||||
|
|
||||||
|
msgid "Accounts Receivable Ageing"
|
||||||
|
msgstr "Старение дебиторской задолженности"
|
||||||
|
|
||||||
|
msgid "Delivery Trends"
|
||||||
|
msgstr "Тренды доставок"
|
||||||
|
|
||||||
|
msgid "Incoming Bills (Purchase Invoice)"
|
||||||
|
msgstr "Входящие счета (Счёт на покупку)"
|
||||||
|
|
||||||
|
msgid "Incoming Leads"
|
||||||
|
msgstr "Входящие лиды"
|
||||||
|
|
||||||
|
msgid "Item Shortage Summary"
|
||||||
|
msgstr "Сводка по дефициту позиций"
|
||||||
|
|
||||||
|
msgid "Item-wise Annual Sales"
|
||||||
|
msgstr "Годовые продажи по позициям"
|
||||||
|
|
||||||
|
msgid "Location-wise Asset Value"
|
||||||
|
msgstr "Стоимость активов по локациям"
|
||||||
|
|
||||||
|
msgid "Material Request Analysis"
|
||||||
|
msgstr "Анализ заявок на материалы"
|
||||||
|
|
||||||
|
msgid "Oldest Items"
|
||||||
|
msgstr "Старейшие позиции"
|
||||||
|
|
||||||
|
msgid "Opportunities via Campaigns"
|
||||||
|
msgstr "Возможности по кампаниям"
|
||||||
|
|
||||||
|
msgid "Opportunity Trends"
|
||||||
|
msgstr "Тренды возможностей"
|
||||||
|
|
||||||
|
msgid "Outgoing Bills (Sales Invoice)"
|
||||||
|
msgstr "Исходящие счета (Счёт на продажу)"
|
||||||
|
|
||||||
|
msgid "Territory Wise Opportunity Count"
|
||||||
|
msgstr "Количество возможностей по территориям"
|
||||||
|
|
||||||
|
msgid "Territory Wise Sales"
|
||||||
|
msgstr "Продажи по территориям"
|
||||||
|
|
||||||
|
msgid "Top Customers"
|
||||||
|
msgstr "Топ клиентов"
|
||||||
|
|
||||||
|
msgid "Top Suppliers"
|
||||||
|
msgstr "Топ поставщиков"
|
||||||
|
|
||||||
|
msgid "Warehouse wise Stock Value"
|
||||||
|
msgstr "Стоимость склада по складам"
|
||||||
|
|
||||||
|
# === Dashboard parents ===
|
||||||
|
msgid "Project"
|
||||||
|
msgstr "Проект"
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: az_locale extras\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"Language-Team: Russian\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#. Hardcoded literal at frappe/public/js/frappe/list/list_filter.js:88
|
||||||
|
#. wrapped by __() only at render time (line 162), so not picked up by
|
||||||
|
#. bench generate-pot-file. Provide translation here so __() finds it.
|
||||||
|
msgid "Save Current Filter"
|
||||||
|
msgstr "Сохранить текущий фильтр"
|
||||||
Loading…
Reference in New Issue