469 lines
40 KiB
JavaScript
469 lines
40 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const root = path.resolve(__dirname, '..');
|
|
const localeDir = path.join(root, 'src', 'i18n', 'locales');
|
|
const langs = ['ar', 'fr', 'hi', 'ms'];
|
|
|
|
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
const en = readJson(path.join(localeDir, 'en.json'));
|
|
|
|
const flatten = (source, prefix = '', out = {}) => {
|
|
for (const [key, value] of Object.entries(source)) {
|
|
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
flatten(value, fullKey, out);
|
|
} else {
|
|
out[fullKey] = value;
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
|
|
const setDeep = (target, dottedKey, value) => {
|
|
const parts = dottedKey.split('.');
|
|
let current = target;
|
|
for (const part of parts.slice(0, -1)) {
|
|
current[part] ??= {};
|
|
current = current[part];
|
|
}
|
|
current[parts.at(-1)] = value;
|
|
};
|
|
|
|
const enFlat = flatten(en);
|
|
|
|
const exact = {
|
|
ar: {
|
|
'This action cannot be undone.': 'لا يمكن التراجع عن هذا الإجراء.',
|
|
'This action cannot be undone!': 'لا يمكن التراجع عن هذا الإجراء!',
|
|
'Overall Information on Single Screen': 'معلومات عامة في شاشة واحدة',
|
|
'Validation Error': 'خطأ في التحقق',
|
|
'Access Denied': 'تم رفض الوصول',
|
|
'Authenticating...': 'جار التحقق من الهوية...',
|
|
'Change Language': 'تغيير اللغة',
|
|
'Discount(-)': 'الخصم(-)',
|
|
'Sent / Open': 'مرسل / مفتوح',
|
|
'Mark as Sent': 'وضع علامة كمرسل',
|
|
'Convert to Sales Order': 'تحويل إلى أمر بيع',
|
|
'All customers': 'كل العملاء',
|
|
'Select date': 'اختر التاريخ',
|
|
'Search by Quotation #': 'البحث برقم عرض السعر',
|
|
},
|
|
fr: {
|
|
'This action cannot be undone.': 'Cette action ne peut pas etre annulee.',
|
|
'This action cannot be undone!': 'Cette action ne peut pas etre annulee!',
|
|
'Overall Information on Single Screen': 'Informations generales sur un seul ecran',
|
|
'Validation Error': 'Erreur de validation',
|
|
'Access Denied': 'Acces refuse',
|
|
'Authenticating...': 'Authentification...',
|
|
'Change Language': 'Changer de langue',
|
|
'Discount(-)': 'Remise(-)',
|
|
'Sent / Open': 'Envoye / ouvert',
|
|
'Mark as Sent': 'Marquer comme envoye',
|
|
'Convert to Sales Order': 'Convertir en commande client',
|
|
'All customers': 'Tous les clients',
|
|
'Select date': 'Selectionner une date',
|
|
'Search by Quotation #': 'Rechercher par devis #',
|
|
},
|
|
hi: {
|
|
'This action cannot be undone.': 'इस कार्रवाई को वापस नहीं किया जा सकता।',
|
|
'This action cannot be undone!': 'इस कार्रवाई को वापस नहीं किया जा सकता!',
|
|
'Overall Information on Single Screen': 'एक ही स्क्रीन पर पूरी जानकारी',
|
|
'Validation Error': 'सत्यापन त्रुटि',
|
|
'Access Denied': 'पहुंच अस्वीकृत',
|
|
'Authenticating...': 'प्रमाणीकरण हो रहा है...',
|
|
'Change Language': 'भाषा बदलें',
|
|
'Discount(-)': 'छूट(-)',
|
|
'Sent / Open': 'भेजा गया / खुला',
|
|
'Mark as Sent': 'भेजा गया चिह्नित करें',
|
|
'Convert to Sales Order': 'बिक्री आदेश में बदलें',
|
|
'All customers': 'सभी ग्राहक',
|
|
'Select date': 'तारीख चुनें',
|
|
'Search by Quotation #': 'कोटेशन # से खोजें',
|
|
},
|
|
ms: {
|
|
'This action cannot be undone.': 'Tindakan ini tidak boleh dibuat asal.',
|
|
'This action cannot be undone!': 'Tindakan ini tidak boleh dibuat asal!',
|
|
'Overall Information on Single Screen': 'Maklumat keseluruhan pada satu skrin',
|
|
'Validation Error': 'Ralat pengesahan',
|
|
'Access Denied': 'Akses ditolak',
|
|
'Authenticating...': 'Mengesahkan...',
|
|
'Change Language': 'Tukar bahasa',
|
|
'Discount(-)': 'Diskaun(-)',
|
|
'Sent / Open': 'Dihantar / dibuka',
|
|
'Mark as Sent': 'Tanda sebagai dihantar',
|
|
'Convert to Sales Order': 'Tukar kepada pesanan jualan',
|
|
'All customers': 'Semua pelanggan',
|
|
'Select date': 'Pilih tarikh',
|
|
'Search by Quotation #': 'Cari mengikut sebut harga #',
|
|
},
|
|
};
|
|
|
|
const wordMaps = {
|
|
ar: {
|
|
access: 'وصول', account: 'حساب', accounts: 'حسابات', action: 'إجراء', actions: 'إجراءات', active: 'نشط',
|
|
add: 'إضافة', added: 'تمت الإضافة', address: 'عنوان', admin: 'مسؤول', all: 'كل', amount: 'مبلغ',
|
|
and: 'و', apply: 'تطبيق', approved: 'معتمد', available: 'متاح', back: 'رجوع', balance: 'رصيد',
|
|
bank: 'بنك', barcode: 'باركود', brand: 'علامة تجارية', brands: 'علامات تجارية', cancel: 'إلغاء',
|
|
cancelled: 'ملغى', cannot: 'لا يمكن', card: 'بطاقة', cart: 'سلة', cash: 'نقد', categories: 'فئات',
|
|
category: 'فئة', change: 'تغيير', charge: 'رسوم', charges: 'رسوم', city: 'مدينة', close: 'إغلاق',
|
|
closed: 'مغلق', code: 'رمز', company: 'شركة', comparison: 'مقارنة', completed: 'مكتمل', confirm: 'تأكيد',
|
|
contact: 'اتصال', country: 'دولة', create: 'إنشاء', created: 'تم الإنشاء', customer: 'عميل',
|
|
customers: 'عملاء', dashboard: 'لوحة التحكم', date: 'تاريخ', delete: 'حذف', deleted: 'تم الحذف',
|
|
delivery: 'تسليم', description: 'وصف', details: 'تفاصيل', discount: 'خصم', draft: 'مسودة', due: 'مستحق',
|
|
edit: 'تعديل', email: 'بريد إلكتروني', enter: 'أدخل', error: 'خطأ', expense: 'مصروف', expenses: 'مصروفات',
|
|
expired: 'منتهي', failed: 'فشل', filter: 'تصفية', for: 'لـ', found: 'موجود', from: 'من', gateway: 'بوابة',
|
|
gateways: 'بوابات', goods: 'بضائع', group: 'مجموعة', information: 'معلومات', inventory: 'مخزون',
|
|
invoice: 'فاتورة', invoices: 'فواتير', item: 'صنف', items: 'أصناف', ledger: 'سجل', list: 'قائمة',
|
|
loading: 'جار التحميل', login: 'تسجيل الدخول', manage: 'إدارة', management: 'إدارة', material: 'مادة',
|
|
method: 'طريقة', methods: 'طرق', mobile: 'جوال', name: 'اسم', new: 'جديد', no: 'لا', note: 'ملاحظة',
|
|
notes: 'ملاحظات', number: 'رقم', of: 'من', on: 'على', open: 'مفتوح', opening: 'افتتاحي', optional: 'اختياري',
|
|
order: 'طلب', orders: 'طلبات', paid: 'مدفوع', password: 'كلمة المرور', payment: 'دفع', payments: 'مدفوعات',
|
|
pending: 'معلق', phone: 'هاتف', please: 'يرجى', pos: 'نقطة البيع', posted: 'مرحل', price: 'سعر',
|
|
print: 'طباعة', product: 'منتج', purchase: 'شراء', quantity: 'كمية', quotation: 'عرض سعر', quotations: 'عروض أسعار',
|
|
receipt: 'إيصال', receipts: 'إيصالات', received: 'مستلم', record: 'تسجيل', reference: 'مرجع', refund: 'استرداد',
|
|
rejected: 'مرفوض', request: 'طلب', requests: 'طلبات', required: 'مطلوب', reset: 'إعادة تعيين', return: 'إرجاع',
|
|
returns: 'مرتجعات', role: 'دور', sale: 'بيع', sales: 'مبيعات', save: 'حفظ', search: 'بحث', select: 'اختر',
|
|
selected: 'محدد', sent: 'مرسل', session: 'جلسة', sessions: 'جلسات', settings: 'إعدادات', sign: 'تسجيل',
|
|
state: 'ولاية', status: 'حالة', stock: 'مخزون', store: 'متجر', stores: 'متاجر', subcategory: 'فئة فرعية',
|
|
submit: 'إرسال', success: 'نجاح', successfully: 'بنجاح', supplier: 'مورد', suppliers: 'موردون', summary: 'ملخص',
|
|
tax: 'ضريبة', taxes: 'ضرائب', terms: 'شروط', this: 'هذا', to: 'إلى', total: 'الإجمالي', transaction: 'معاملة',
|
|
type: 'نوع', undone: 'التراجع', unit: 'وحدة', units: 'وحدات', update: 'تحديث', updated: 'تم التحديث',
|
|
upload: 'رفع', user: 'مستخدم', users: 'مستخدمون', value: 'قيمة', view: 'عرض', want: 'تريد', warning: 'تحذير',
|
|
with: 'مع', you: 'أنت', your: 'الخاص بك',
|
|
},
|
|
fr: {
|
|
access: 'acces', account: 'compte', accounts: 'comptes', action: 'action', actions: 'actions', active: 'actif',
|
|
add: 'ajouter', added: 'ajoute', address: 'adresse', admin: 'administrateur', all: 'tous', amount: 'montant',
|
|
and: 'et', apply: 'appliquer', approved: 'approuve', available: 'disponible', back: 'retour', balance: 'solde',
|
|
bank: 'banque', barcode: 'code-barres', brand: 'marque', brands: 'marques', cancel: 'annuler', cancelled: 'annule',
|
|
cannot: 'ne peut pas', card: 'carte', cart: 'panier', cash: 'especes', categories: 'categories', category: 'categorie',
|
|
change: 'changer', charge: 'frais', charges: 'frais', city: 'ville', close: 'fermer', closed: 'ferme', code: 'code',
|
|
company: 'entreprise', comparison: 'comparaison', completed: 'termine', confirm: 'confirmer', contact: 'contact',
|
|
country: 'pays', create: 'creer', created: 'cree', customer: 'client', customers: 'clients', dashboard: 'tableau de bord',
|
|
date: 'date', delete: 'supprimer', deleted: 'supprime', delivery: 'livraison', description: 'description',
|
|
details: 'details', discount: 'remise', draft: 'brouillon', due: 'du', edit: 'modifier', email: 'e-mail',
|
|
enter: 'saisir', error: 'erreur', expense: 'depense', expenses: 'depenses', expired: 'expire', failed: 'echec',
|
|
filter: 'filtrer', for: 'pour', found: 'trouve', from: 'de', gateway: 'passerelle', gateways: 'passerelles',
|
|
goods: 'marchandises', group: 'groupe', information: 'information', inventory: 'stock', invoice: 'facture',
|
|
invoices: 'factures', item: 'article', items: 'articles', ledger: 'grand livre', list: 'liste', loading: 'chargement',
|
|
login: 'connexion', manage: 'gerer', management: 'gestion', material: 'materiel', method: 'methode', methods: 'methodes',
|
|
mobile: 'mobile', name: 'nom', new: 'nouveau', no: 'aucun', note: 'note', notes: 'notes', number: 'numero',
|
|
of: 'de', on: 'sur', open: 'ouvert', opening: 'ouverture', optional: 'facultatif', order: 'commande',
|
|
orders: 'commandes', paid: 'paye', password: 'mot de passe', payment: 'paiement', payments: 'paiements',
|
|
pending: 'en attente', phone: 'telephone', please: 'veuillez', pos: 'caisse', posted: 'valide', price: 'prix',
|
|
print: 'imprimer', product: 'produit', purchase: 'achat', quantity: 'quantite', quotation: 'devis', quotations: 'devis',
|
|
receipt: 'recu', receipts: 'recus', received: 'recu', record: 'enregistrer', reference: 'reference', refund: 'remboursement',
|
|
rejected: 'rejete', request: 'demande', requests: 'demandes', required: 'obligatoire', reset: 'reinitialiser',
|
|
return: 'retour', returns: 'retours', role: 'role', sale: 'vente', sales: 'ventes', save: 'enregistrer',
|
|
search: 'rechercher', select: 'selectionner', selected: 'selectionne', sent: 'envoye', session: 'session',
|
|
sessions: 'sessions', settings: 'parametres', sign: 'se connecter', state: 'etat', status: 'statut',
|
|
stock: 'stock', store: 'magasin', stores: 'magasins', subcategory: 'sous-categorie', submit: 'soumettre',
|
|
success: 'succes', successfully: 'avec succes', supplier: 'fournisseur', suppliers: 'fournisseurs', summary: 'resume',
|
|
tax: 'taxe', taxes: 'taxes', terms: 'conditions', this: 'ceci', to: 'a', total: 'total', transaction: 'transaction',
|
|
type: 'type', undone: 'annule', unit: 'unite', units: 'unites', update: 'mettre a jour', updated: 'mis a jour',
|
|
upload: 'televerser', user: 'utilisateur', users: 'utilisateurs', value: 'valeur', view: 'voir', want: 'voulez',
|
|
warning: 'avertissement', with: 'avec', you: 'vous', your: 'votre',
|
|
},
|
|
hi: {
|
|
access: 'पहुंच', account: 'खाता', accounts: 'खाते', action: 'क्रिया', actions: 'क्रियाएं', active: 'सक्रिय',
|
|
add: 'जोड़ें', added: 'जोड़ा गया', address: 'पता', admin: 'प्रशासक', all: 'सभी', amount: 'राशि',
|
|
and: 'और', apply: 'लागू करें', approved: 'स्वीकृत', available: 'उपलब्ध', back: 'वापस', balance: 'शेष',
|
|
bank: 'बैंक', barcode: 'बारकोड', brand: 'ब्रांड', brands: 'ब्रांड', cancel: 'रद्द करें', cancelled: 'रद्द',
|
|
cannot: 'नहीं किया जा सकता', card: 'कार्ड', cart: 'कार्ट', cash: 'नकद', categories: 'श्रेणियां',
|
|
category: 'श्रेणी', change: 'बदलें', charge: 'शुल्क', charges: 'शुल्क', city: 'शहर', close: 'बंद करें',
|
|
closed: 'बंद', code: 'कोड', company: 'कंपनी', comparison: 'तुलना', completed: 'पूर्ण', confirm: 'पुष्टि करें',
|
|
contact: 'संपर्क', country: 'देश', create: 'बनाएं', created: 'बनाया गया', customer: 'ग्राहक', customers: 'ग्राहक',
|
|
dashboard: 'डैशबोर्ड', date: 'तारीख', delete: 'हटाएं', deleted: 'हटाया गया', delivery: 'डिलीवरी',
|
|
description: 'विवरण', details: 'विवरण', discount: 'छूट', draft: 'मसौदा', due: 'बकाया', edit: 'संपादित करें',
|
|
email: 'ईमेल', enter: 'दर्ज करें', error: 'त्रुटि', expense: 'खर्च', expenses: 'खर्च', expired: 'समाप्त',
|
|
failed: 'विफल', filter: 'फिल्टर', for: 'के लिए', found: 'मिला', from: 'से', gateway: 'गेटवे', gateways: 'गेटवे',
|
|
goods: 'माल', group: 'समूह', information: 'जानकारी', inventory: 'इन्वेंटरी', invoice: 'चालान', invoices: 'चालान',
|
|
item: 'आइटम', items: 'आइटम', ledger: 'लेजर', list: 'सूची', loading: 'लोड हो रहा है', login: 'लॉगिन',
|
|
manage: 'प्रबंधित करें', management: 'प्रबंधन', material: 'सामग्री', method: 'विधि', methods: 'विधियां',
|
|
mobile: 'मोबाइल', name: 'नाम', new: 'नया', no: 'कोई नहीं', note: 'नोट', notes: 'नोट्स', number: 'नंबर',
|
|
of: 'का', on: 'पर', open: 'खुला', opening: 'आरंभिक', optional: 'वैकल्पिक', order: 'आदेश',
|
|
orders: 'आदेश', paid: 'भुगतान किया', password: 'पासवर्ड', payment: 'भुगतान', payments: 'भुगतान',
|
|
pending: 'लंबित', phone: 'फोन', please: 'कृपया', pos: 'पीओएस', posted: 'पोस्ट किया गया', price: 'कीमत',
|
|
print: 'प्रिंट', product: 'उत्पाद', purchase: 'खरीद', quantity: 'मात्रा', quotation: 'कोटेशन', quotations: 'कोटेशन',
|
|
receipt: 'रसीद', receipts: 'रसीदें', received: 'प्राप्त', record: 'रिकॉर्ड', reference: 'संदर्भ', refund: 'रिफंड',
|
|
rejected: 'अस्वीकृत', request: 'अनुरोध', requests: 'अनुरोध', required: 'आवश्यक', reset: 'रीसेट',
|
|
return: 'वापसी', returns: 'वापसी', role: 'भूमिका', sale: 'बिक्री', sales: 'बिक्री', save: 'सहेजें',
|
|
search: 'खोजें', select: 'चुनें', selected: 'चयनित', sent: 'भेजा गया', session: 'सत्र', sessions: 'सत्र',
|
|
settings: 'सेटिंग्स', sign: 'साइन', state: 'राज्य', status: 'स्थिति', stock: 'स्टॉक', store: 'स्टोर',
|
|
stores: 'स्टोर', subcategory: 'उपश्रेणी', submit: 'जमा करें', success: 'सफलता', successfully: 'सफलतापूर्वक',
|
|
supplier: 'आपूर्तिकर्ता', suppliers: 'आपूर्तिकर्ता', summary: 'सारांश', tax: 'कर', taxes: 'कर',
|
|
terms: 'शर्तें', this: 'यह', to: 'तक', total: 'कुल', transaction: 'लेनदेन', type: 'प्रकार',
|
|
undone: 'वापस', unit: 'इकाई', units: 'इकाइयां', update: 'अपडेट', updated: 'अपडेट किया गया',
|
|
upload: 'अपलोड', user: 'उपयोगकर्ता', users: 'उपयोगकर्ता', value: 'मूल्य', view: 'देखें', want: 'चाहते हैं',
|
|
warning: 'चेतावनी', with: 'साथ', you: 'आप', your: 'आपका',
|
|
},
|
|
ms: {
|
|
access: 'akses', account: 'akaun', accounts: 'akaun', action: 'tindakan', actions: 'tindakan', active: 'aktif',
|
|
add: 'tambah', added: 'ditambah', address: 'alamat', admin: 'pentadbir', all: 'semua', amount: 'jumlah',
|
|
and: 'dan', apply: 'guna', approved: 'diluluskan', available: 'tersedia', back: 'kembali', balance: 'baki',
|
|
bank: 'bank', barcode: 'kod bar', brand: 'jenama', brands: 'jenama', cancel: 'batal', cancelled: 'dibatalkan',
|
|
cannot: 'tidak boleh', card: 'kad', cart: 'troli', cash: 'tunai', categories: 'kategori', category: 'kategori',
|
|
change: 'tukar', charge: 'caj', charges: 'caj', city: 'bandar', close: 'tutup', closed: 'ditutup', code: 'kod',
|
|
company: 'syarikat', comparison: 'perbandingan', completed: 'selesai', confirm: 'sahkan', contact: 'hubungan',
|
|
country: 'negara', create: 'cipta', created: 'dicipta', customer: 'pelanggan', customers: 'pelanggan',
|
|
dashboard: 'papan pemuka', date: 'tarikh', delete: 'padam', deleted: 'dipadam', delivery: 'penghantaran',
|
|
description: 'penerangan', details: 'butiran', discount: 'diskaun', draft: 'draf', due: 'tertunggak',
|
|
edit: 'sunting', email: 'e-mel', enter: 'masukkan', error: 'ralat', expense: 'perbelanjaan',
|
|
expenses: 'perbelanjaan', expired: 'tamat tempoh', failed: 'gagal', filter: 'tapis', for: 'untuk',
|
|
found: 'ditemui', from: 'dari', gateway: 'gerbang', gateways: 'gerbang', goods: 'barang', group: 'kumpulan',
|
|
information: 'maklumat', inventory: 'inventori', invoice: 'invois', invoices: 'invois', item: 'item',
|
|
items: 'item', ledger: 'lejar', list: 'senarai', loading: 'memuatkan', login: 'log masuk', manage: 'urus',
|
|
management: 'pengurusan', material: 'bahan', method: 'kaedah', methods: 'kaedah', mobile: 'mudah alih',
|
|
name: 'nama', new: 'baharu', no: 'tiada', note: 'nota', notes: 'nota', number: 'nombor', of: 'daripada',
|
|
on: 'pada', open: 'buka', opening: 'pembukaan', optional: 'pilihan', order: 'pesanan', orders: 'pesanan',
|
|
paid: 'dibayar', password: 'kata laluan', payment: 'bayaran', payments: 'bayaran', pending: 'tertunda',
|
|
phone: 'telefon', please: 'sila', pos: 'POS', posted: 'dipos', price: 'harga', print: 'cetak',
|
|
product: 'produk', purchase: 'pembelian', quantity: 'kuantiti', quotation: 'sebut harga',
|
|
quotations: 'sebut harga', receipt: 'resit', receipts: 'resit', received: 'diterima', record: 'rekod',
|
|
reference: 'rujukan', refund: 'bayaran balik', rejected: 'ditolak', request: 'permintaan', requests: 'permintaan',
|
|
required: 'diperlukan', reset: 'tetapkan semula', return: 'pulangan', returns: 'pulangan', role: 'peranan',
|
|
sale: 'jualan', sales: 'jualan', save: 'simpan', search: 'cari', select: 'pilih', selected: 'dipilih',
|
|
sent: 'dihantar', session: 'sesi', sessions: 'sesi', settings: 'tetapan', sign: 'daftar', state: 'negeri',
|
|
status: 'status', stock: 'stok', store: 'kedai', stores: 'kedai', subcategory: 'subkategori',
|
|
submit: 'hantar', success: 'berjaya', successfully: 'berjaya', supplier: 'pembekal', suppliers: 'pembekal',
|
|
summary: 'ringkasan', tax: 'cukai', taxes: 'cukai', terms: 'terma', this: 'ini', to: 'ke',
|
|
total: 'jumlah', transaction: 'transaksi', type: 'jenis', undone: 'dibuat asal', unit: 'unit',
|
|
units: 'unit', update: 'kemas kini', updated: 'dikemas kini', upload: 'muat naik', user: 'pengguna',
|
|
users: 'pengguna', value: 'nilai', view: 'lihat', want: 'mahu', warning: 'amaran', with: 'dengan',
|
|
you: 'anda', your: 'anda',
|
|
},
|
|
};
|
|
|
|
const keepUpper = new Set(['API', 'CSV', 'DN', 'GRN', 'GST', 'GSTIN', 'ID', 'MR', 'PDF', 'PO', 'POS', 'QR', 'RFQ', 'SKU', 'SMS', 'UPI', 'VAT']);
|
|
|
|
const extraWords = {
|
|
ar: {
|
|
abandoned: 'مهجورة', annually: 'سنويا', analyze: 'تحليل', application: 'تطبيق', approval: 'موافقة',
|
|
assigned: 'معين', authorised: 'مفوض', authorized: 'مفوض', auto: 'تلقائي', avg: 'متوسط', awaiting: 'بانتظار',
|
|
before: 'قبل', bid: 'عرض', bids: 'عروض', bootstrapping: 'تهيئة', businesses: 'أعمال', calculated: 'محسوب',
|
|
carts: 'سلات', choose: 'اختر', clear: 'مسح', cleanup: 'تنظيف', column: 'عمود', confidence: 'ثقة',
|
|
contain: 'تحتوي', counter: 'كاونتر', days: 'أيام', deadline: 'موعد نهائي', demo: 'عرض توضيحي', disc: 'خصم',
|
|
document: 'مستند', domain: 'نطاق', extraction: 'استخراج', fee: 'رسوم', file: 'ملف', files: 'ملفات',
|
|
first: 'أول', floor: 'طابق', format: 'تنسيق', free: 'مجاني', generated: 'مولد', get: 'ابدأ',
|
|
guidelines: 'إرشادات', headers: 'رؤوس', identifier: 'معرف', image: 'صورة', import: 'استيراد',
|
|
instructions: 'تعليمات', internal: 'داخلي', landmark: 'معلم', last: 'آخر', link: 'رابط', main: 'رئيسي',
|
|
margin: 'هامش', me: 'تذكرني', minimum: 'الحد الأدنى', month: 'شهر', monthly: 'شهريا', more: 'المزيد',
|
|
multiple: 'متعددة', near: 'قرب', never: 'أبدا', numbers: 'أرقام', off: 'تقريب', older: 'أقدم',
|
|
online: 'متصل', pagination: 'ترقيم الصفحات', permanent: 'دائم', physical: 'فعلي', pin: 'رمز', platform: 'منصة',
|
|
policies: 'سياسات', postcode: 'الرمز البريدي', preview: 'معاينة', profit: 'ربح', published: 'منشور',
|
|
quick: 'سريع', qty: 'الكمية', recommended: 'موصى به', registration: 'تسجيل', remember: 'تذكرني',
|
|
remove: 'إزالة', resetting: 'إعادة التعيين', response: 'استجابة', retention: 'احتفاظ', retry: 'إعادة المحاولة',
|
|
row: 'صف', sac: 'SAC', saved: 'محفوظ', secret: 'سر', selection: 'اختيار', sending: 'جار الإرسال',
|
|
share: 'مشاركة', should: 'يجب', signature: 'توقيع', source: 'مصدر', started: 'ابدأ', subtotal: 'المجموع الفرعي',
|
|
supports: 'يدعم', than: 'من', title: 'عنوان', today: 'اليوم', trail: 'سجل', transactions: 'معاملات',
|
|
trial: 'تجربة', until: 'حتى', valid: 'صالح', verifying: 'جار التحقق', verify: 'تحقق', watch: 'شاهد',
|
|
webhook: 'Webhook', website: 'موقع إلكتروني', weekly: 'أسبوعيا', wipe: 'مسح', yesterday: 'أمس', zip: 'ZIP',
|
|
},
|
|
fr: {
|
|
abandoned: 'abandonnes', annually: 'annuellement', analyze: 'analyser', application: 'application',
|
|
approval: 'approbation', assigned: 'assigne', authorised: 'signataire autorise', authorized: 'autorise',
|
|
auto: 'auto', avg: 'moy.', awaiting: 'en attente', before: 'avant', bid: 'offre', bids: 'offres',
|
|
bootstrapping: 'demarrage', businesses: 'entreprises', calculated: 'calcule', carts: 'paniers',
|
|
choose: 'choisir', clear: 'effacer', cleanup: 'nettoyage', column: 'colonne', confidence: 'confiance',
|
|
contain: 'contenir', counter: 'comptoir', days: 'jours', deadline: 'echeance', demo: 'demo', disc: 'rem.',
|
|
document: 'document', domain: 'domaine', extraction: 'extraction', fee: 'frais', file: 'fichier',
|
|
files: 'fichiers', first: 'premiere', floor: 'etage', format: 'format', free: 'gratuit', generated: 'genere',
|
|
get: 'commencer', guidelines: 'consignes', headers: 'en-tetes', identifier: 'identifiant', image: 'image',
|
|
instructions: 'instructions', internal: 'interne', landmark: 'repere', last: 'dernier', link: 'lien',
|
|
main: 'principal', margin: 'marge', me: 'moi', minimum: 'minimum', month: 'mois', monthly: 'mensuel',
|
|
more: 'plus', multiple: 'multiples', near: 'pres de', never: 'jamais', numbers: 'nombres', off: 'arrondi',
|
|
older: 'plus anciens', online: 'en ligne', pagination: 'pagination', permanent: 'permanent', physical: 'physique',
|
|
pin: 'PIN', platform: 'plateforme', policies: 'politiques', postcode: 'code postal', preview: 'apercu',
|
|
profit: 'benefice', published: 'publie', quick: 'rapide', qty: 'qte', recommended: 'recommande',
|
|
registration: 'inscription', remember: 'se souvenir', remove: 'supprimer', resetting: 'reinitialisation',
|
|
response: 'reponse', retention: 'conservation', retry: 'reessayer', row: 'ligne', saved: 'enregistre',
|
|
secret: 'secret', selection: 'selection', sending: 'envoi', share: 'partager', should: 'doit',
|
|
signature: 'signature', source: 'source', started: 'demarrer', subtotal: 'sous-total', supports: 'prend en charge',
|
|
than: 'que', title: 'titre', today: 'aujourd hui', trail: 'piste', transactions: 'transactions',
|
|
trial: 'essai', until: 'jusqu au', valid: 'valide', verifying: 'verification', verify: 'verifier',
|
|
watch: 'regarder', webhook: 'secret webhook', website: 'site web', weekly: 'hebdomadaire', wipe: 'effacement',
|
|
yesterday: 'hier', zip: 'ZIP',
|
|
},
|
|
hi: {
|
|
abandoned: 'छोड़े गए', annually: 'वार्षिक', analyze: 'विश्लेषण करें', application: 'एप्लिकेशन',
|
|
approval: 'अनुमोदन', assigned: 'असाइन किया गया', authorised: 'अधिकृत', authorized: 'अधिकृत',
|
|
auto: 'स्वतः', avg: 'औसत', awaiting: 'प्रतीक्षा में', before: 'पहले', bid: 'बोली', bids: 'बोलियां',
|
|
bootstrapping: 'आरंभ हो रहा है', businesses: 'व्यवसाय', calculated: 'गणना किया गया', carts: 'कार्ट',
|
|
choose: 'चुनें', clear: 'साफ करें', cleanup: 'सफाई', column: 'कॉलम', confidence: 'विश्वास',
|
|
contain: 'होना चाहिए', counter: 'काउंटर', days: 'दिन', deadline: 'अंतिम तिथि', demo: 'डेमो', disc: 'छूट',
|
|
document: 'दस्तावेज', domain: 'डोमेन', extraction: 'निकासी', fee: 'शुल्क', file: 'फाइल', files: 'फाइलें',
|
|
first: 'पहली', floor: 'मंजिल', format: 'फॉर्मेट', free: 'मुफ्त', generated: 'जनरेट किया गया',
|
|
get: 'शुरू करें', guidelines: 'दिशानिर्देश', headers: 'हेडर', identifier: 'पहचानकर्ता', image: 'छवि',
|
|
import: 'आयात', instructions: 'निर्देश', internal: 'आंतरिक', landmark: 'लैंडमार्क', last: 'पिछला',
|
|
link: 'लिंक', main: 'मुख्य', margin: 'मार्जिन', me: 'मुझे', minimum: 'न्यूनतम', month: 'महीना',
|
|
monthly: 'मासिक', more: 'अधिक', multiple: 'कई', near: 'पास', never: 'कभी नहीं', numbers: 'नंबर',
|
|
off: 'राउंड ऑफ', older: 'से पुराने', online: 'ऑनलाइन', pagination: 'पेजिनेशन', permanent: 'स्थायी',
|
|
physical: 'भौतिक', pin: 'पिन', platform: 'प्लेटफॉर्म', policies: 'नीतियां', postcode: 'पोस्टकोड',
|
|
preview: 'पूर्वावलोकन', profit: 'लाभ', published: 'प्रकाशित', quick: 'त्वरित', qty: 'मात्रा',
|
|
recommended: 'अनुशंसित', registration: 'पंजीकरण', remember: 'याद रखें', remove: 'हटाएं',
|
|
resetting: 'रीसेट हो रहा है', response: 'प्रतिक्रिया', retention: 'रिटेंशन', retry: 'फिर कोशिश करें',
|
|
row: 'पंक्ति', saved: 'सहेजा गया', secret: 'सीक्रेट', selection: 'चयन', sending: 'भेजा जा रहा है',
|
|
share: 'साझा करें', should: 'होना चाहिए', signature: 'हस्ताक्षर', source: 'स्रोत', started: 'शुरू करें',
|
|
subtotal: 'उप-योग', supports: 'समर्थित', than: 'से', title: 'शीर्षक', today: 'आज', trail: 'ट्रेल',
|
|
transactions: 'लेनदेन', trial: 'ट्रायल', until: 'तक', valid: 'मान्य', verifying: 'सत्यापित हो रहा है',
|
|
verify: 'सत्यापित करें', watch: 'देखें', webhook: 'वेबहुक', website: 'वेबसाइट', weekly: 'साप्ताहिक',
|
|
wipe: 'वाइप', yesterday: 'कल', zip: 'ZIP',
|
|
},
|
|
ms: {
|
|
abandoned: 'terbiar', annually: 'tahunan', analyze: 'analisis', application: 'aplikasi', approval: 'kelulusan',
|
|
assigned: 'ditugaskan', authorised: 'dibenarkan', authorized: 'dibenarkan', auto: 'auto', avg: 'purata',
|
|
awaiting: 'menunggu', before: 'sebelum', bid: 'bidaan', bids: 'bidaan', bootstrapping: 'memulakan',
|
|
businesses: 'perniagaan', calculated: 'dikira', carts: 'troli', choose: 'pilih', clear: 'kosongkan',
|
|
cleanup: 'pembersihan', column: 'lajur', confidence: 'keyakinan', contain: 'mengandungi', counter: 'kaunter',
|
|
days: 'hari', deadline: 'tarikh akhir', demo: 'demo', disc: 'diskaun', document: 'dokumen',
|
|
domain: 'domain', extraction: 'pengekstrakan', fee: 'fi', file: 'fail', files: 'fail', first: 'pertama',
|
|
floor: 'tingkat', format: 'format', free: 'percuma', generated: 'dijana', get: 'mula',
|
|
guidelines: 'panduan', headers: 'tajuk lajur', identifier: 'pengenal', image: 'imej', instructions: 'arahan',
|
|
internal: 'dalaman', landmark: 'mercu tanda', last: 'terakhir', link: 'pautan', main: 'utama',
|
|
margin: 'margin', me: 'saya', minimum: 'minimum', month: 'bulan', monthly: 'bulanan', more: 'lagi',
|
|
multiple: 'berbilang', near: 'berhampiran', never: 'tidak pernah', numbers: 'nombor', off: 'bundar',
|
|
older: 'lebih lama', online: 'dalam talian', pagination: 'halaman', permanent: 'kekal', physical: 'fizikal',
|
|
pin: 'PIN', platform: 'platform', policies: 'polisi', postcode: 'poskod', preview: 'pratonton',
|
|
profit: 'untung', published: 'diterbitkan', quick: 'pantas', qty: 'kuantiti', recommended: 'disyorkan',
|
|
registration: 'pendaftaran', remember: 'ingat', remove: 'buang', resetting: 'menetapkan semula',
|
|
response: 'respons', retention: 'pengekalan', retry: 'cuba lagi', row: 'baris', saved: 'disimpan',
|
|
secret: 'rahsia', selection: 'pilihan', sending: 'menghantar', share: 'kongsi', should: 'harus',
|
|
signature: 'tandatangan', source: 'sumber', started: 'bermula', subtotal: 'subtotal', supports: 'menyokong',
|
|
than: 'daripada', title: 'tajuk', today: 'hari ini', trail: 'jejak', transactions: 'transaksi',
|
|
trial: 'percubaan', until: 'hingga', valid: 'sah', verifying: 'mengesahkan', verify: 'sahkan',
|
|
watch: 'tonton', webhook: 'webhook', website: 'laman web', weekly: 'mingguan', wipe: 'hapus',
|
|
yesterday: 'semalam', zip: 'ZIP',
|
|
},
|
|
};
|
|
|
|
for (const lang of Object.keys(extraWords)) {
|
|
Object.assign(wordMaps[lang], extraWords[lang]);
|
|
}
|
|
|
|
const moreWords = {
|
|
ar: {
|
|
actual: 'فعلي', another: 'آخر', apartment: 'شقة', applied: 'مطبق', attached: 'مرفق', authentication: 'مصادقة',
|
|
breakdown: 'تفصيل', channels: 'قنوات', check: 'فحص', closing: 'إغلاق', code: 'رمز', compared: 'مقارن',
|
|
converted: 'محول', counters: 'كاونترات', damaged: 'تالف', discrepancies: 'فروقات', exceeded: 'تم التجاوز',
|
|
export: 'تصدير', final: 'نهائي', finalize: 'إنهاء', finalized: 'منتهي', gross: 'إجمالي', hash: 'رقم',
|
|
identity: 'هوية', integrated: 'متكامل', invitation: 'دعوة', invited: 'مدعو', taxable: 'خاضع للضريبة',
|
|
key: 'مفتاح', lifecycle: 'دورة الحياة', line: 'سطر', lines: 'أسطر', marked: 'تم وضع علامة', match: 'متطابقة',
|
|
mode: 'وضع', net: 'صافي', notice: 'إشعار', opened: 'تم الفتح', otp: 'OTP', outstanding: 'مستحق',
|
|
owner: 'مالك', paying: 'جار الدفع', permission: 'صلاحيات', permissions: 'صلاحيات', portal: 'بوابة',
|
|
prepayment: 'دفعة مقدمة', proof: 'إثبات', protocols: 'بروتوكولات', refundable: 'قابل للاسترداد',
|
|
recording: 'تسجيل', roles: 'أدوار', secret: 'سر', send: 'إرسال', shipping: 'شحن', suite: 'جناح',
|
|
swift: 'SWIFT', terminal: 'طرفية', unit: 'وحدة', variance: 'تباين', verification: 'تحقق',
|
|
},
|
|
fr: {
|
|
actual: 'reel', another: 'autre', apartment: 'appartement', applied: 'applique', attached: 'jointe',
|
|
authentication: 'authentification', breakdown: 'repartition', channels: 'canaux', check: 'controle',
|
|
closing: 'cloture', code: 'code', compared: 'compare', converted: 'converti', counters: 'comptoirs',
|
|
damaged: 'endommage', discrepancies: 'ecarts', exceeded: 'depassee', export: 'export', final: 'final',
|
|
finalize: 'finaliser', finalized: 'finalise', gross: 'brut', identity: 'identite', integrated: 'integre',
|
|
invitation: 'invitation', taxable: 'taxable', key: 'cle', lifecycle: 'cycle de vie', line: 'ligne',
|
|
lines: 'lignes', marked: 'marque', match: 'correspondent', mode: 'mode', net: 'net', notice: 'avis',
|
|
opened: 'ouvert', otp: 'OTP', outstanding: 'restant', owner: 'responsable', paying: 'paiement',
|
|
permission: 'autorisations', permissions: 'autorisations', portal: 'portail', prepayment: 'acompte',
|
|
proof: 'preuve', protocols: 'protocoles', refundable: 'remboursable', recording: 'enregistrement',
|
|
roles: 'roles', secret: 'secret', send: 'envoyer', shipping: 'expedition', suite: 'suite', swift: 'SWIFT',
|
|
terminal: 'terminal', unit: 'unite', variance: 'ecart', verification: 'verification',
|
|
},
|
|
hi: {
|
|
actual: 'वास्तविक', another: 'दूसरा', apartment: 'अपार्टमेंट', applied: 'लागू', attached: 'संलग्न',
|
|
authentication: 'प्रमाणीकरण', breakdown: 'विवरण', channels: 'चैनल', check: 'जांच', closing: 'समापन',
|
|
code: 'कोड', compared: 'तुलना किया गया', converted: 'बदला गया', counters: 'काउंटर', damaged: 'क्षतिग्रस्त',
|
|
discrepancies: 'अंतर', exceeded: 'सीमा पार', export: 'निर्यात', final: 'अंतिम', finalize: 'अंतिम करें',
|
|
finalized: 'अंतिम किया गया', gross: 'सकल', identity: 'पहचान', integrated: 'एकीकृत', invitation: 'आमंत्रण',
|
|
taxable: 'कर योग्य', key: 'कुंजी', lifecycle: 'जीवनचक्र', line: 'लाइन', lines: 'लाइनें',
|
|
marked: 'चिह्नित', match: 'मिलते हैं', mode: 'मोड', net: 'नेट', notice: 'सूचना', opened: 'खोला गया',
|
|
otp: 'OTP', outstanding: 'बकाया', owner: 'स्वामी', paying: 'भुगतान हो रहा है', permission: 'अनुमति',
|
|
permissions: 'अनुमतियां', portal: 'पोर्टल', prepayment: 'अग्रिम भुगतान', proof: 'प्रमाण',
|
|
protocols: 'प्रोटोकॉल', refundable: 'रिफंड योग्य', recording: 'रिकॉर्डिंग', roles: 'भूमिकाएं',
|
|
secret: 'सीक्रेट', send: 'भेजें', shipping: 'शिपिंग', suite: 'सुइट', swift: 'SWIFT', terminal: 'टर्मिनल',
|
|
unit: 'इकाई', variance: 'अंतर', verification: 'सत्यापन',
|
|
},
|
|
ms: {
|
|
actual: 'sebenar', another: 'lain', apartment: 'apartmen', applied: 'digunakan', attached: 'dilampirkan',
|
|
authentication: 'pengesahan', breakdown: 'pecahan', channels: 'saluran', check: 'semakan',
|
|
closing: 'penutupan', code: 'kod', compared: 'dibandingkan', converted: 'ditukar', counters: 'kaunter',
|
|
damaged: 'rosak', discrepancies: 'percanggahan', exceeded: 'melebihi had', export: 'eksport',
|
|
final: 'akhir', finalize: 'muktamadkan', finalized: 'dimuktamadkan', gross: 'kasar', identity: 'identiti',
|
|
integrated: 'bersepadu', invitation: 'jemputan', taxable: 'boleh dicukai', key: 'kunci',
|
|
lifecycle: 'kitaran hayat', line: 'baris', lines: 'baris', marked: 'ditanda', match: 'sepadan',
|
|
mode: 'mod', net: 'bersih', notice: 'notis', opened: 'dibuka', otp: 'OTP', outstanding: 'tertunggak',
|
|
owner: 'pemilik', paying: 'membayar', permission: 'kebenaran', permissions: 'kebenaran',
|
|
portal: 'portal', prepayment: 'prabayaran', proof: 'bukti', protocols: 'protokol',
|
|
refundable: 'boleh dibayar balik', recording: 'rakaman', roles: 'peranan', secret: 'rahsia',
|
|
send: 'hantar', shipping: 'penghantaran', suite: 'suite', swift: 'SWIFT', terminal: 'terminal',
|
|
unit: 'unit', variance: 'varians', verification: 'pengesahan',
|
|
},
|
|
};
|
|
|
|
for (const lang of Object.keys(moreWords)) {
|
|
Object.assign(wordMaps[lang], moreWords[lang]);
|
|
}
|
|
|
|
const applyCase = (source, translated) => {
|
|
if (keepUpper.has(source.toUpperCase())) return source.toUpperCase();
|
|
if (source === source.toUpperCase() && source.length > 1) return translated.toUpperCase();
|
|
if (/^[A-Z]/.test(source) && /^[a-z]/.test(translated)) {
|
|
return translated.charAt(0).toUpperCase() + translated.slice(1);
|
|
}
|
|
return translated;
|
|
};
|
|
|
|
const translateLoose = (value, lang) => {
|
|
if (typeof value !== 'string') return value;
|
|
if (exact[lang]?.[value]) return exact[lang][value];
|
|
|
|
const placeholders = [];
|
|
let work = value.replace(/\{\{[^}]+\}\}/g, (match) => {
|
|
const token = `__PH_${placeholders.length}__`;
|
|
placeholders.push(match);
|
|
return token;
|
|
});
|
|
|
|
const map = wordMaps[lang];
|
|
work = work.replace(/[A-Za-z][A-Za-z']*/g, (word) => {
|
|
if (/^__PH_\d+__$/.test(word)) return word;
|
|
const translated = map[word.toLowerCase()];
|
|
return translated ? applyCase(word, translated) : word;
|
|
});
|
|
|
|
placeholders.forEach((placeholder, index) => {
|
|
work = work.replace(`__PH_${index}__`, placeholder);
|
|
});
|
|
|
|
return work;
|
|
};
|
|
|
|
for (const lang of langs) {
|
|
const file = path.join(localeDir, `${lang}.json`);
|
|
const locale = readJson(file);
|
|
const localeFlat = flatten(locale);
|
|
|
|
const translatedByEnglishValue = new Map();
|
|
for (const [key, enValue] of Object.entries(enFlat)) {
|
|
const localeValue = localeFlat[key];
|
|
if (typeof enValue !== 'string' || typeof localeValue !== 'string') continue;
|
|
if (localeValue !== enValue) translatedByEnglishValue.set(enValue, localeValue);
|
|
}
|
|
|
|
let changed = 0;
|
|
for (const [key, enValue] of Object.entries(enFlat)) {
|
|
if (typeof enValue !== 'string') continue;
|
|
const current = localeFlat[key];
|
|
if (current !== undefined && current !== enValue) continue;
|
|
|
|
const replacement = translatedByEnglishValue.get(enValue) ?? translateLoose(enValue, lang);
|
|
if (replacement !== current) {
|
|
setDeep(locale, key, replacement);
|
|
changed += 1;
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync(file, `${JSON.stringify(locale, null, 2)}\n`, 'utf8');
|
|
console.log(`${lang}: backfilled ${changed} entries`);
|
|
}
|