diff --git a/frontend/public/locales/en-GB/common.json b/frontend/public/locales/en-GB/common.json new file mode 100644 index 0000000..fca9592 --- /dev/null +++ b/frontend/public/locales/en-GB/common.json @@ -0,0 +1,483 @@ +{ + "language": { + "id": "Bahasa Indonesia", + "en-GB": "English UK" + }, + "app": { + "title": "Aquaculture Ops CRUD", + "description": "Multi-tenant aquaculture operations and marketplace CRUD with RBAC, orders, transactions, audit logs, and APIs." + }, + "pages": { + "dashboard": { + "pageTitle": "Dashboard", + "overview": "Overview", + "loadingWidgets": "Loading widgets...", + "loading": "Loading..." + }, + "login": { + "pageTitle": "Login", + "sampleCredentialsAdmin": "Use {{email}} / {{password}} to log in as Administrator", + "sampleCredentialsUser": "Use {{email}} / {{password}} to log in as User", + "form": { + "loginLabel": "Login", + "loginHelp": "Please enter your login", + "passwordLabel": "Password", + "passwordHelp": "Please enter your password", + "remember": "Remember", + "forgotPassword": "Forgot password?", + "loginButton": "Login", + "loading": "Loading...", + "noAccountYet": "Don’t have an account yet?", + "newAccount": "New account" + }, + "pexels": { + "photoCredit": "Photo by {{photographer}} on Pexels", + "videoCredit": "Video by {{name}} on Pexels", + "videoUnsupported": "Your browser does not support the video tag." + }, + "footer": { + "copyright": "© {{year}} {{title}}. All rights reserved", + "privacy": "Privacy Policy" + } + } + }, + "components": { + "widgetCreator": { + "title": "Create chart or widget", + "helpText": "Describe your new widget or chart in natural language. For example: \"Number of admin users\" OR \"red chart with number of closed contracts grouped by month\"", + "settingsTitle": "Widget Creator settings", + "settingsDescription": "What role are we showing and creating widgets for?", + "doneButton": "Done", + "loading": "Loading..." + }, + "search": { + "placeholder": "Search", + "required": "Required", + "minLength": "Minimum length: {{count}} characters" + } + }, + "labels": { + "overview": "Overview", + "dashboard": "Dashboard", + "farm_ops_command_center": "Farm Ops Command Centre", + "multi_tenant_aquaculture_workflow": "Multi-tenant aquaculture workflow", + "record_feed_monitor_appetite_and_keep_the_farm_team_aligned": "Record feed, monitor appetite, and keep the farm team aligned.", + "a_focused_daily_operations_slice_built_on_top_of_your_generated_crud_entities_batches_feed_products_and_feeding_logs": "A focused daily operations slice built on top of your generated CRUD entities: batches, feed products, and feeding logs.", + "fed_today": "Fed today", + "active_batches": "Active batches", + "latest_appetite": "Latest appetite", + "quick_feed_entry": "Quick feed entry", + "record_the_most_common_daily_farm_action_without_leaving_the_operations_context": "Record the most common daily farm action without leaving the operations context.", + "saving": "Saving...", + "loading_farm_data": "Loading farm data...", + "recent_activity": "Recent activity", + "latest_feeding_logs_from_the_generated_crud_api": "Latest feeding logs from the generated CRUD API.", + "record_snapshot": "Record snapshot", + "detail": "Detail", + "batch_context": "Batch context", + "daily_rhythm": "Daily rhythm", + "marketplace_ready": "Marketplace ready", + "every_record_is_tied_to_generated_batch_crud_for_traceability": "Every record is tied to generated batch CRUD for traceability.", + "fast_input_supports_the_staff_workflow_farmers_repeat_every_shift": "Fast input supports the staff workflow farmers repeat every shift.", + "operational_records_are_one_step_away_from_harvest_listing_order_and_transaction_flows": "Operational records are one step away from harvest, listing, order, and transaction flows.", + "no_feeding_logs_yet": "No feeding logs yet", + "use_the_quick_entry_form_to_create_the_first_daily_operation_record": "Use the quick entry form to create the first daily operation record.", + "select_a_feeding_log_to_inspect_its_farm_ready_details": "Select a feeding log to inspect its farm-ready details.", + "no_notes_added_for_this_operation": "No notes added for this operation.", + "team_member": "Team member", + "unassigned_batch": "Unassigned batch", + "not_specified": "Not specified", + "not_scheduled": "Not scheduled", + "batch_before_recording_feed": "Choose a batch before recording feed.", + "add_the_feeding_time": "Add the feeding time.", + "feed_quantity_must_be_greater_than_0_kg": "Feed quantity must be greater than 0 kg.", + "feeding_record_saved_the_latest_activity_list_has_been_refreshed": "Feeding record saved. The latest activity list has been refreshed.", + "could_not_load_the_farm_operations_workspace_please_refresh_or_check_your_permissions": "Could not load the farm operations workspace. Please refresh or check your permissions.", + "could_not_save_the_feeding_record_please_check_the_values_and_try_again": "Could not save the feeding record. Please check the values and try again.", + "you_can_view_existing_data_but_need_create_feeding_logs_permission_to_save_new_records": "You can view existing data, but need Create Feeding Logs permission to save new records.", + "create_your_first_batch_before_recording_feed": "Create your first batch before recording feed.", + "select_a_batch": "Select a batch", + "select_feed_product_optional": "Select feed product (optional)", + "optional_notes_for_the_next_shift": "Optional notes for the next shift", + "page": "Page", + "of": "of", + "please_confirm": "Please confirm", + "are_you_sure_you_want_to_delete_this_item": "Are you sure you want to delete this item?", + "allowed_formats": "Allowed formats: {{formats}}", + "click_to_upload": "Click to upload", + "or_drag_and_drop": "or drag and drop", + "switch_to_table": "Switch to table", + "switch_to_cards": "Switch to cards", + "users_widgets": "{{role}}'s widgets", + "no_data": "No data", + "matches_with": "Matches with: {{query}}", + "no_matches": "No matches", + "quick_input": "Quick input", + "log_a_feeding": "Log a feeding", + "capture_the_minimum_operational_data_and_confirm_it_immediately": "Capture the minimum operational data and confirm it immediately.", + "loading_batches_and_feed_products": "Loading batches and feed products...", + "no_batches_available_yet": "No batches available yet.", + "create_a_batch_first_then_return_here_to_record_daily_feeding_activity": "Create a batch first, then return here to record daily feeding activity.", + "optional_observation_e_g_fish_surfaced_quickly_mild_leftovers_weather_change": "Optional observation, e.g. fish surfaced quickly, mild leftovers, weather change...", + "your_role_can_view_feeding_logs_but_cannot_create_new_records": "Your role can view feeding logs but cannot create new records.", + "activity_stream": "Activity stream", + "latest_feeding_logs": "Latest feeding logs", + "loading_latest_records": "Loading latest records...", + "use_the_quick_input_form_to_create_the_first_daily_operation_record": "Use the quick input form to create the first daily operation record.", + "by": "by", + "recorded": "Recorded", + "feed": "Feed", + "method": "Method", + "choose_a_batch_before_recording_feed": "Choose a batch before recording feed.", + "login": "Login", + "password": "Password", + "please_enter_your_login": "Please enter your login", + "please_enter_your_password": "Please enter your password", + "remember": "Remember", + "loading": "Loading..." + }, + "crud": { + "actions": { + "new": "New", + "view": "View", + "edit": "Edit", + "delete": "Delete", + "submit": "Submit", + "reset": "Reset", + "cancel": "Cancel", + "confirm": "Confirm", + "filter": "Filter", + "apply": "Apply", + "download_csv": "Download CSV", + "upload_csv": "Upload CSV", + "new_item": "New item", + "open_full_crud": "Open full CRUD", + "open_detail": "Open detail", + "marketplace": "Marketplace", + "delete_rows": "Delete {{count}} row(s)", + "deleting": "Deleting...", + "select_value": "Select value", + "from": "From", + "to": "To", + "contains": "Contains", + "contained": "Contained", + "value": "Value", + "action": "Action", + "create_batch": "Create batch", + "save_feeding_record": "Save feeding record", + "advanced_form": "Advanced form", + "refresh": "Refresh" + } + }, + "fields": { + "id": "ID", + "name": "Name", + "first_name": "First name", + "lastname": "Last name", + "last_name": "Last name", + "phone_number": "Phone number", + "email": "Email", + "disabled": "Disabled", + "password": "Password", + "provider": "Provider", + "custom_permissions": "Custom permissions", + "custom_permissions_filter": "Custom permissions filter", + "email_verified": "Email verified", + "email_verification_token": "Email verification token", + "email_verification_token_expires_at": "Email verification token expires at", + "password_reset_token": "Password reset token", + "password_reset_token_expires_at": "Password reset token expires at", + "app_role": "App role", + "avatar": "Avatar", + "created_by": "Created by", + "updated_by": "Updated by", + "created_at": "Created at", + "updated_at": "Updated at", + "organizations": "Organisations", + "organization": "Organisation", + "tenant": "Tenant", + "location": "Location", + "pond": "Pond", + "batch": "Batch", + "species": "Species", + "user": "User", + "role": "Role", + "permissions": "Permissions", + "permission": "Permission", + "pond_name": "Pond name", + "pond_type": "Pond type", + "area_sq_m": "Area (sq m)", + "average_depth_m": "Average depth (m)", + "avg_depth_m": "Average depth (m)", + "notes": "Notes", + "status": "Status", + "species_name": "Species name", + "scientific_name": "Scientific name", + "typical_harvest_size": "Typical harvest size", + "batch_code": "Batch code", + "stocked_at": "Stocked at", + "initial_count": "Initial count", + "initial_avg_weight_g": "Initial average weight (g)", + "initial_average_weight_g": "Initial average weight (g)", + "batch_status": "Batch status", + "expected_harvest_at": "Expected harvest at", + "product_name": "Product name", + "brand": "Brand", + "feed_type": "Feed type", + "protein_percent": "Protein %", + "fat_percent": "Fat %", + "pellet_size_mm": "Pellet size (mm)", + "fed_at": "Fed at", + "quantity_kg": "Quantity (kg)", + "feeding_method": "Feeding method", + "appetite": "Appetite", + "recorded_by": "Recorded by", + "sampled_at": "Sampled at", + "temperature_c": "Temperature (°C)", + "ph": "pH", + "dissolved_oxygen_mg_l": "Dissolved oxygen (mg/L)", + "salinity_ppt": "Salinity (ppt)", + "ammonia_mg_l": "Ammonia (mg/L)", + "nitrite_mg_l": "Nitrite (mg/L)", + "nitrate_mg_l": "Nitrate (mg/L)", + "turbidity_ntu": "Turbidity (NTU)", + "observed_at": "Observed at", + "event_type": "Event type", + "severity": "Severity", + "mortality_count": "Mortality count", + "symptoms": "Symptoms", + "treatment": "Treatment", + "resolution_status": "Resolution status", + "resolved_at": "Resolved at", + "reported_by": "Reported by", + "attachments": "Attachments", + "item_category": "Item category", + "item_name": "Item name", + "sku": "SKU", + "unit": "Unit", + "quantity_on_hand": "Quantity on hand", + "reorder_level": "Reorder level", + "unit_cost": "Unit cost", + "inventory_item": "Inventory item", + "feed_product": "Feed product", + "movement_type": "Movement type", + "quantity": "Quantity", + "moved_at": "Moved at", + "reference": "Reference", + "source": "Source", + "destination": "Destination", + "harvested_at": "Harvested at", + "total_weight_kg": "Total weight (kg)", + "total_count": "Total count", + "avg_weight_g": "Average weight (g)", + "grade": "Grade", + "listing_title": "Listing title", + "description": "Description", + "product_form": "Product form", + "available_quantity_kg": "Available quantity (kg)", + "price_per_kg": "Price per kg", + "currency": "Currency", + "available_from": "Available from", + "expires_at": "Expires at", + "listing_status": "Listing status", + "seller_tenant": "Seller tenant", + "buyer_tenant": "Buyer tenant", + "farm_tenant": "Farm tenant", + "investor_tenant": "Investor tenant", + "harvest": "Harvest", + "fulfillment_location": "Fulfilment location", + "photos": "Photos", + "order_number": "Order number", + "ordered_at": "Ordered at", + "order_status": "Order status", + "subtotal_amount": "Subtotal amount", + "tax_amount": "Tax amount", + "shipping_amount": "Shipping amount", + "total_amount": "Total amount", + "delivery_address": "Delivery address", + "buyer_notes": "Buyer notes", + "expected_delivery_at": "Expected delivery at", + "listing": "Listing", + "order": "Order", + "unit_price": "Unit price", + "line_total": "Line total", + "transaction_type": "Transaction type", + "transaction_status": "Transaction status", + "amount": "Amount", + "provider_reference": "Provider reference", + "processed_at": "Processed at", + "failure_reason": "Failure reason", + "shipment_status": "Shipment status", + "carrier": "Carrier", + "tracking_number": "Tracking number", + "status_at": "Status at", + "status_details": "Status details", + "investment_name": "Investment name", + "instrument_type": "Instrument type", + "amount_committed": "Amount committed", + "amount_funded": "Amount funded", + "committed_at": "Committed at", + "funded_at": "Funded at", + "investment_status": "Investment status", + "to_email": "To email", + "subject": "Subject", + "template_key": "Template key", + "payload": "Payload", + "send_status": "Send status", + "sent_at": "Sent at", + "recipient_user": "Recipient user", + "action": "Action", + "entity_name": "Entity name", + "entity_reference": "Entity reference", + "occurred_at": "Occurred at", + "ip_address": "IP address", + "user_agent": "User agent", + "change_summary": "Change summary", + "actor_user": "Actor user", + "key_name": "Key name", + "key_prefix": "Key prefix", + "access_level": "Access level", + "last_used_at": "Last used at", + "is_revoked": "Is revoked", + "endpoint_name": "Endpoint name", + "url": "URL", + "secret": "Secret", + "is_enabled": "Is enabled", + "event_types": "Event types", + "last_delivery_at": "Last delivery at", + "endpoint": "Endpoint", + "event_reference": "Event reference", + "delivered_at": "Delivered at", + "delivery_status": "Delivery status", + "http_status": "HTTP status", + "request_payload": "Request payload", + "response_body": "Response body", + "attempt_count": "Attempt count", + "job_type": "Job type", + "job_status": "Job status", + "total_rows": "Total rows", + "processed_rows": "Processed rows", + "error_rows": "Error rows", + "error_report": "Error report", + "started_at": "Started at", + "finished_at": "Finished at", + "requested_by": "Requested by", + "source_file": "Source file", + "result_file": "Result file", + "membership_status": "Membership status", + "joined_at": "Joined at", + "tenant_name": "Tenant name", + "slug": "Slug", + "tenant_type": "Tenant type", + "contact_email": "Contact email", + "contact_phone": "Contact phone", + "billing_address": "Billing address", + "subscription_status": "Subscription status", + "trial_ends_at": "Trial ends at", + "location_name": "Location name", + "address": "Address", + "latitude": "Latitude", + "longitude": "Longitude", + "timezone": "Time zone", + "is_primary": "Is primary", + "role_customization": "Role customisation", + "global_access": "Global access", + "permissions_filter": "Permissions filter", + "multi_text": "Multi text" + }, + "entities": { + "dashboard": "Dashboard", + "farm_ops": "Farm Ops", + "users": "Users", + "user": "User", + "roles": "Roles", + "role": "Role", + "permissions": "Permissions", + "permission": "Permission", + "organizations": "Organisations", + "organization": "Organisation", + "tenants": "Tenants", + "tenant": "Tenant", + "user_memberships": "User memberships", + "user_membership": "User membership", + "tenant_locations": "Tenant locations", + "tenant_location": "Tenant location", + "ponds": "Ponds", + "pond": "Pond", + "species": "Species", + "batches": "Batches", + "batch": "Batch", + "feed_products": "Feed products", + "feed_product": "Feed product", + "feeding_logs": "Feeding logs", + "feeding_log": "Feeding log", + "water_quality_logs": "Water quality logs", + "water_quality_log": "Water quality log", + "health_events": "Health events", + "health_event": "Health event", + "inventory_items": "Inventory items", + "inventory_item": "Inventory item", + "inventory_movements": "Inventory movements", + "inventory_movement": "Inventory movement", + "harvests": "Harvests", + "harvest": "Harvest", + "marketplace_listings": "Marketplace listings", + "marketplace_listing": "Marketplace listing", + "orders": "Orders", + "order": "Order", + "order_items": "Order items", + "order_item": "Order item", + "payment_transactions": "Payment transactions", + "payment_transaction": "Payment transaction", + "shipment_updates": "Shipment updates", + "shipment_update": "Shipment update", + "investments": "Investments", + "investment": "Investment", + "email_notifications": "Email notifications", + "email_notification": "Email notification", + "audit_logs": "Audit logs", + "audit_log": "Audit log", + "api_keys": "API keys", + "api_key": "API key", + "webhook_endpoints": "Webhook endpoints", + "webhook_endpoint": "Webhook endpoint", + "webhook_deliveries": "Webhook deliveries", + "webhook_delivery": "Webhook delivery", + "csv_jobs": "CSV jobs", + "csv_job": "CSV job" + }, + "enums": { + "active": "Active", + "inactive": "Inactive", + "maintenance": "Maintenance", + "planned": "Planned", + "harvested": "Harvested", + "closed": "Closed", + "lost": "Lost", + "manual": "Manual", + "auto_feeder": "Auto feeder", + "broadcast": "Broadcast", + "tray": "Tray", + "low": "Low", + "normal": "Normal", + "high": "High", + "pending": "Pending", + "confirmed": "Confirmed", + "packed": "Packed", + "shipped": "Shipped", + "delivered": "Delivered", + "canceled": "Cancelled", + "refunded": "Refunded", + "draft": "Draft", + "paused": "Paused", + "sold_out": "Sold out", + "expired": "Expired", + "removed": "Removed", + "earthen": "Earthen", + "lined": "Lined", + "tank": "Tank", + "cage": "Cage", + "raceway": "Raceway", + "other": "Other" + } +} diff --git a/frontend/public/locales/id/common.json b/frontend/public/locales/id/common.json new file mode 100644 index 0000000..4be3d45 --- /dev/null +++ b/frontend/public/locales/id/common.json @@ -0,0 +1,484 @@ +{ + "language": { + "id": "Bahasa Indonesia", + "en-GB": "English UK" + }, + "app": { + "title": "CRUD Operasi Akuakultur", + "description": "CRUD operasi akuakultur dan marketplace multi-tenant dengan RBAC, pesanan, transaksi, log audit, dan API." + }, + "pages": { + "dashboard": { + "pageTitle": "Dasbor", + "overview": "Ringkasan", + "loadingWidgets": "Memuat widget...", + "loading": "Memuat..." + }, + "login": { + "pageTitle": "Masuk", + "sampleCredentialsAdmin": "Gunakan {{email}} / {{password}} untuk masuk sebagai Administrator", + "sampleCredentialsUser": "Gunakan {{email}} / {{password}} untuk masuk sebagai Pengguna", + "form": { + "loginLabel": "Login", + "loginHelp": "Masukkan login Anda", + "passwordLabel": "Kata sandi", + "passwordHelp": "Masukkan kata sandi Anda", + "remember": "Ingat saya", + "forgotPassword": "Lupa kata sandi?", + "loginButton": "Masuk", + "loading": "Memuat...", + "noAccountYet": "Belum punya akun?", + "newAccount": "Akun baru" + }, + "pexels": { + "photoCredit": "Foto oleh {{photographer}} di Pexels", + "videoCredit": "Video oleh {{name}} di Pexels", + "videoUnsupported": "Browser Anda tidak mendukung tag video." + }, + "footer": { + "copyright": "© {{year}} {{title}}. Semua hak dilindungi", + "privacy": "Kebijakan Privasi" + } + } + }, + "components": { + "widgetCreator": { + "title": "Buat grafik atau widget", + "helpText": "Jelaskan widget atau grafik baru dengan bahasa natural. Contoh: \"Jumlah pengguna admin\" ATAU \"grafik merah berisi jumlah kontrak selesai dikelompokkan per bulan\"", + "settingsTitle": "Pengaturan pembuat widget", + "settingsDescription": "Widget ditampilkan dan dibuat untuk peran apa?", + "doneButton": "Selesai", + "loading": "Memuat..." + }, + "search": { + "placeholder": "Cari", + "required": "Wajib", + "minLength": "Panjang minimum: {{count}} karakter" + } + }, + "labels": { + "overview": "Ringkasan", + "dashboard": "Dasbor", + "farm_ops_command_center": "Pusat Komando Operasi Tambak", + "multi_tenant_aquaculture_workflow": "Alur kerja akuakultur multi-tenant", + "record_feed_monitor_appetite_and_keep_the_farm_team_aligned": "Catat pakan, pantau nafsu makan, dan selaraskan tim tambak.", + "a_focused_daily_operations_slice_built_on_top_of_your_generated_crud_entities_batches_feed_products_and_feeding_logs": "Bagian operasi harian yang fokus, dibangun di atas entitas CRUD yang sudah dibuat: batch, produk pakan, dan log pemberian pakan.", + "fed_today": "Pakan hari ini", + "active_batches": "Batch aktif", + "latest_appetite": "Nafsu makan terbaru", + "quick_feed_entry": "Input pakan cepat", + "record_the_most_common_daily_farm_action_without_leaving_the_operations_context": "Catat aktivitas harian tambak yang paling umum tanpa keluar dari konteks operasi.", + "saving": "Menyimpan...", + "loading_farm_data": "Memuat data tambak...", + "recent_activity": "Aktivitas terbaru", + "latest_feeding_logs_from_the_generated_crud_api": "Log pemberian pakan terbaru dari API CRUD yang sudah dibuat.", + "record_snapshot": "Ringkasan catatan", + "detail": "Detail", + "batch_context": "Konteks batch", + "daily_rhythm": "Ritme harian", + "marketplace_ready": "Siap marketplace", + "every_record_is_tied_to_generated_batch_crud_for_traceability": "Setiap catatan terhubung ke CRUD batch untuk keterlacakan.", + "fast_input_supports_the_staff_workflow_farmers_repeat_every_shift": "Input cepat mendukung alur kerja staf yang diulang petani setiap shift.", + "operational_records_are_one_step_away_from_harvest_listing_order_and_transaction_flows": "Catatan operasi selangkah lagi dari alur panen, listing, pesanan, dan transaksi.", + "no_feeding_logs_yet": "Belum ada log pemberian pakan", + "use_the_quick_entry_form_to_create_the_first_daily_operation_record": "Gunakan formulir input cepat untuk membuat catatan operasi harian pertama.", + "select_a_feeding_log_to_inspect_its_farm_ready_details": "Pilih log pemberian pakan untuk melihat detail siap pakai di tambak.", + "no_notes_added_for_this_operation": "Belum ada catatan untuk operasi ini.", + "team_member": "Anggota tim", + "unassigned_batch": "Batch belum ditetapkan", + "not_specified": "Belum ditentukan", + "not_scheduled": "Belum dijadwalkan", + "batch_before_recording_feed": "Pilih batch sebelum mencatat pakan.", + "add_the_feeding_time": "Tambahkan waktu pemberian pakan.", + "feed_quantity_must_be_greater_than_0_kg": "Jumlah pakan harus lebih dari 0 kg.", + "feeding_record_saved_the_latest_activity_list_has_been_refreshed": "Catatan pakan tersimpan. Daftar aktivitas terbaru telah diperbarui.", + "could_not_load_the_farm_operations_workspace_please_refresh_or_check_your_permissions": "Tidak dapat memuat ruang kerja operasi tambak. Silakan muat ulang atau periksa izin Anda.", + "could_not_save_the_feeding_record_please_check_the_values_and_try_again": "Tidak dapat menyimpan catatan pakan. Periksa nilai yang diisi lalu coba lagi.", + "you_can_view_existing_data_but_need_create_feeding_logs_permission_to_save_new_records": "Anda dapat melihat data yang ada, tetapi perlu izin Buat Log Pemberian Pakan untuk menyimpan catatan baru.", + "create_your_first_batch_before_recording_feed": "Buat batch pertama sebelum mencatat pakan.", + "select_a_batch": "Pilih batch", + "select_feed_product_optional": "Pilih produk pakan (opsional)", + "optional_notes_for_the_next_shift": "Catatan opsional untuk shift berikutnya", + "page": "Halaman", + "of": "dari", + "please_confirm": "Mohon konfirmasi", + "are_you_sure_you_want_to_delete_this_item": "Apakah Anda yakin ingin menghapus item ini?", + "allowed_formats": "Format yang diizinkan: {{formats}}", + "click_to_upload": "Klik untuk mengunggah", + "or_drag_and_drop": "atau seret dan lepas", + "switch_to_table": "Beralih ke tabel", + "switch_to_cards": "Beralih ke kartu", + "users_widgets": "Widget milik {{role}}", + "no_data": "Tidak ada data", + "matches_with": "Cocok dengan: {{query}}", + "no_matches": "Tidak ada hasil", + "quick_input": "Input cepat", + "log_a_feeding": "Catat pemberian pakan", + "capture_the_minimum_operational_data_and_confirm_it_immediately": "Isi data operasi minimum dan konfirmasi langsung.", + "loading_batches_and_feed_products": "Memuat batch dan produk pakan...", + "no_batches_available_yet": "Belum ada batch.", + "create_a_batch_first_then_return_here_to_record_daily_feeding_activity": "Buat batch terlebih dahulu, lalu kembali ke sini untuk mencatat aktivitas pakan harian.", + "optional_observation_e_g_fish_surfaced_quickly_mild_leftovers_weather_change": "Observasi opsional, mis. ikan cepat naik, sedikit sisa pakan, perubahan cuaca...", + "your_role_can_view_feeding_logs_but_cannot_create_new_records": "Peran Anda dapat melihat log pakan, tetapi tidak dapat membuat catatan baru.", + "activity_stream": "Alur aktivitas", + "latest_feeding_logs": "Log pakan terbaru", + "loading_latest_records": "Memuat catatan terbaru...", + "use_the_quick_input_form_to_create_the_first_daily_operation_record": "Gunakan formulir input cepat untuk membuat catatan operasi harian pertama.", + "by": "oleh", + "recorded": "Dicatat", + "feed": "Pakan", + "method": "Metode", + "choose_a_batch_before_recording_feed": "Pilih batch sebelum mencatat pakan.", + "login": "Masuk", + "password": "Kata sandi", + "please_enter_your_login": "Masukkan login Anda", + "please_enter_your_password": "Masukkan kata sandi Anda", + "remember": "Ingat saya", + "loading": "Memuat..." + }, + "crud": { + "actions": { + "new": "Buat", + "view": "Lihat", + "edit": "Edit", + "delete": "Hapus", + "submit": "Simpan", + "reset": "Reset", + "cancel": "Batal", + "confirm": "Konfirmasi", + "filter": "Filter", + "apply": "Terapkan", + "download_csv": "Unduh CSV", + "upload_csv": "Unggah CSV", + "new_item": "Item baru", + "open_full_crud": "Buka CRUD lengkap", + "open_detail": "Buka detail", + "marketplace": "Marketplace", + "delete_rows": "Hapus {{count}} baris", + "deleting": "Menghapus...", + "select_value": "Pilih nilai", + "from": "Dari", + "to": "Sampai", + "contains": "Berisi", + "contained": "Berisi", + "value": "Nilai", + "action": "Aksi", + "create_batch": "Buat batch", + "save_feeding_record": "Simpan catatan pakan", + "advanced_form": "Form lanjutan", + "refresh": "Segarkan" + } + }, + "fields": { + "id": "ID", + "name": "Nama", + "first_name": "Nama depan", + "firstname": "Nama depan", + "last_name": "Nama belakang", + "lastname": "Nama belakang", + "phone_number": "Nomor telepon", + "email": "Email", + "disabled": "Dinonaktifkan", + "password": "Kata sandi", + "provider": "Penyedia", + "custom_permissions": "Izin khusus", + "custom_permissions_filter": "Filter izin khusus", + "email_verified": "Email terverifikasi", + "email_verification_token": "Token verifikasi email", + "email_verification_token_expires_at": "Token verifikasi email kedaluwarsa pada", + "password_reset_token": "Token reset kata sandi", + "password_reset_token_expires_at": "Token reset kata sandi kedaluwarsa pada", + "app_role": "Peran aplikasi", + "avatar": "Avatar", + "created_by": "Dibuat oleh", + "updated_by": "Diperbarui oleh", + "created_at": "Dibuat pada", + "updated_at": "Diperbarui pada", + "organizations": "Organisasi", + "organization": "Organisasi", + "tenant": "Tenant", + "location": "Lokasi", + "pond": "Kolam", + "batch": "Batch", + "species": "Spesies", + "user": "Pengguna", + "role": "Peran", + "permissions": "Izin", + "permission": "Izin", + "pond_name": "Nama kolam", + "pond_type": "Tipe kolam", + "area_sq_m": "Luas (m²)", + "average_depth_m": "Kedalaman rata-rata (m)", + "avg_depth_m": "Kedalaman rata-rata (m)", + "notes": "Catatan", + "status": "Status", + "species_name": "Nama spesies", + "scientific_name": "Nama ilmiah", + "typical_harvest_size": "Ukuran panen umum", + "batch_code": "Kode batch", + "stocked_at": "Tanggal tebar", + "initial_count": "Jumlah awal", + "initial_avg_weight_g": "Bobot rata-rata awal (g)", + "initial_average_weight_g": "Bobot rata-rata awal (g)", + "batch_status": "Status batch", + "expected_harvest_at": "Perkiraan panen pada", + "product_name": "Nama produk", + "brand": "Merek", + "feed_type": "Jenis pakan", + "protein_percent": "Protein %", + "fat_percent": "Lemak %", + "pellet_size_mm": "Ukuran pelet (mm)", + "fed_at": "Waktu pemberian pakan", + "quantity_kg": "Jumlah (kg)", + "feeding_method": "Metode pemberian pakan", + "appetite": "Nafsu makan", + "recorded_by": "Dicatat oleh", + "sampled_at": "Waktu sampel", + "temperature_c": "Suhu (°C)", + "ph": "pH", + "dissolved_oxygen_mg_l": "Oksigen terlarut (mg/L)", + "salinity_ppt": "Salinitas (ppt)", + "ammonia_mg_l": "Amonia (mg/L)", + "nitrite_mg_l": "Nitrit (mg/L)", + "nitrate_mg_l": "Nitrat (mg/L)", + "turbidity_ntu": "Kekeruhan (NTU)", + "observed_at": "Diamati pada", + "event_type": "Jenis kejadian", + "severity": "Tingkat keparahan", + "mortality_count": "Jumlah kematian", + "symptoms": "Gejala", + "treatment": "Penanganan", + "resolution_status": "Status penyelesaian", + "resolved_at": "Diselesaikan pada", + "reported_by": "Dilaporkan oleh", + "attachments": "Lampiran", + "item_category": "Kategori item", + "item_name": "Nama item", + "sku": "SKU", + "unit": "Satuan", + "quantity_on_hand": "Stok tersedia", + "reorder_level": "Batas pemesanan ulang", + "unit_cost": "Biaya satuan", + "inventory_item": "Item inventaris", + "feed_product": "Produk pakan", + "movement_type": "Jenis pergerakan", + "quantity": "Jumlah", + "moved_at": "Dipindahkan pada", + "reference": "Referensi", + "source": "Sumber", + "destination": "Tujuan", + "harvested_at": "Dipanen pada", + "total_weight_kg": "Berat total (kg)", + "total_count": "Jumlah total", + "avg_weight_g": "Bobot rata-rata (g)", + "grade": "Grade", + "listing_title": "Judul listing", + "description": "Deskripsi", + "product_form": "Bentuk produk", + "available_quantity_kg": "Jumlah tersedia (kg)", + "price_per_kg": "Harga per kg", + "currency": "Mata uang", + "available_from": "Tersedia mulai", + "expires_at": "Kedaluwarsa pada", + "listing_status": "Status listing", + "seller_tenant": "Tenant penjual", + "buyer_tenant": "Tenant pembeli", + "farm_tenant": "Tenant tambak", + "investor_tenant": "Tenant investor", + "harvest": "Panen", + "fulfillment_location": "Lokasi pemenuhan", + "photos": "Foto", + "order_number": "Nomor pesanan", + "ordered_at": "Dipesan pada", + "order_status": "Status pesanan", + "subtotal_amount": "Subtotal", + "tax_amount": "Pajak", + "shipping_amount": "Biaya pengiriman", + "total_amount": "Total", + "delivery_address": "Alamat pengiriman", + "buyer_notes": "Catatan pembeli", + "expected_delivery_at": "Perkiraan pengiriman pada", + "listing": "Listing", + "order": "Pesanan", + "unit_price": "Harga satuan", + "line_total": "Total baris", + "transaction_type": "Jenis transaksi", + "transaction_status": "Status transaksi", + "amount": "Jumlah", + "provider_reference": "Referensi penyedia", + "processed_at": "Diproses pada", + "failure_reason": "Alasan gagal", + "shipment_status": "Status pengiriman", + "carrier": "Kurir", + "tracking_number": "Nomor resi", + "status_at": "Status pada", + "status_details": "Detail status", + "investment_name": "Nama investasi", + "instrument_type": "Jenis instrumen", + "amount_committed": "Jumlah komitmen", + "amount_funded": "Jumlah didanai", + "committed_at": "Dikomitmenkan pada", + "funded_at": "Didanai pada", + "investment_status": "Status investasi", + "to_email": "Email tujuan", + "subject": "Subjek", + "template_key": "Kunci template", + "payload": "Payload", + "send_status": "Status kirim", + "sent_at": "Dikirim pada", + "recipient_user": "Pengguna penerima", + "action": "Aksi", + "entity_name": "Nama entitas", + "entity_reference": "Referensi entitas", + "occurred_at": "Terjadi pada", + "ip_address": "Alamat IP", + "user_agent": "User agent", + "change_summary": "Ringkasan perubahan", + "actor_user": "Pengguna pelaku", + "key_name": "Nama kunci", + "key_prefix": "Awalan kunci", + "access_level": "Level akses", + "last_used_at": "Terakhir digunakan pada", + "is_revoked": "Dicabut", + "endpoint_name": "Nama endpoint", + "url": "URL", + "secret": "Rahasia", + "is_enabled": "Aktif", + "event_types": "Jenis event", + "last_delivery_at": "Pengiriman terakhir pada", + "endpoint": "Endpoint", + "event_reference": "Referensi event", + "delivered_at": "Dikirim pada", + "delivery_status": "Status pengiriman", + "http_status": "Status HTTP", + "request_payload": "Payload permintaan", + "response_body": "Isi respons", + "attempt_count": "Jumlah percobaan", + "job_type": "Jenis pekerjaan", + "job_status": "Status pekerjaan", + "total_rows": "Total baris", + "processed_rows": "Baris diproses", + "error_rows": "Baris error", + "error_report": "Laporan error", + "started_at": "Dimulai pada", + "finished_at": "Selesai pada", + "requested_by": "Diminta oleh", + "source_file": "File sumber", + "result_file": "File hasil", + "membership_status": "Status keanggotaan", + "joined_at": "Bergabung pada", + "tenant_name": "Nama tenant", + "slug": "Slug", + "tenant_type": "Jenis tenant", + "contact_email": "Email kontak", + "contact_phone": "Telepon kontak", + "billing_address": "Alamat penagihan", + "subscription_status": "Status langganan", + "trial_ends_at": "Masa uji coba berakhir pada", + "location_name": "Nama lokasi", + "address": "Alamat", + "latitude": "Lintang", + "longitude": "Bujur", + "timezone": "Zona waktu", + "is_primary": "Utama", + "role_customization": "Kustomisasi peran", + "global_access": "Akses global", + "permissions_filter": "Filter izin", + "multi_text": "Teks tambahan" + }, + "entities": { + "dashboard": "Dasbor", + "farm_ops": "Operasi Tambak", + "users": "Pengguna", + "user": "Pengguna", + "roles": "Peran", + "role": "Peran", + "permissions": "Izin", + "permission": "Izin", + "organizations": "Organisasi", + "organization": "Organisasi", + "tenants": "Tenant", + "tenant": "Tenant", + "user_memberships": "Keanggotaan Pengguna", + "user_membership": "Keanggotaan Pengguna", + "tenant_locations": "Lokasi Tenant", + "tenant_location": "Lokasi Tenant", + "ponds": "Kolam", + "pond": "Kolam", + "species": "Spesies", + "batches": "Batch", + "batch": "Batch", + "feed_products": "Produk Pakan", + "feed_product": "Produk Pakan", + "feeding_logs": "Log Pemberian Pakan", + "feeding_log": "Log Pemberian Pakan", + "water_quality_logs": "Log Kualitas Air", + "water_quality_log": "Log Kualitas Air", + "health_events": "Kejadian Kesehatan", + "health_event": "Kejadian Kesehatan", + "inventory_items": "Item Inventaris", + "inventory_item": "Item Inventaris", + "inventory_movements": "Pergerakan Inventaris", + "inventory_movement": "Pergerakan Inventaris", + "harvests": "Panen", + "harvest": "Panen", + "marketplace_listings": "Listing Marketplace", + "marketplace_listing": "Listing Marketplace", + "orders": "Pesanan", + "order": "Pesanan", + "order_items": "Item Pesanan", + "order_item": "Item Pesanan", + "payment_transactions": "Transaksi Pembayaran", + "payment_transaction": "Transaksi Pembayaran", + "shipment_updates": "Pembaruan Pengiriman", + "shipment_update": "Pembaruan Pengiriman", + "investments": "Investasi", + "investment": "Investasi", + "email_notifications": "Notifikasi Email", + "email_notification": "Notifikasi Email", + "audit_logs": "Log Audit", + "audit_log": "Log Audit", + "api_keys": "Kunci API", + "api_key": "Kunci API", + "webhook_endpoints": "Endpoint Webhook", + "webhook_endpoint": "Endpoint Webhook", + "webhook_deliveries": "Pengiriman Webhook", + "webhook_delivery": "Pengiriman Webhook", + "csv_jobs": "Pekerjaan CSV", + "csv_job": "Pekerjaan CSV" + }, + "enums": { + "active": "Aktif", + "inactive": "Tidak aktif", + "maintenance": "Perawatan", + "planned": "Direncanakan", + "harvested": "Dipanen", + "closed": "Ditutup", + "lost": "Hilang", + "manual": "Manual", + "auto_feeder": "Pemberi pakan otomatis", + "broadcast": "Sebar", + "tray": "Tray", + "low": "Rendah", + "normal": "Normal", + "high": "Tinggi", + "pending": "Menunggu", + "confirmed": "Dikonfirmasi", + "packed": "Dikemas", + "shipped": "Dikirim", + "delivered": "Terkirim", + "canceled": "Dibatalkan", + "refunded": "Dikembalikan dana", + "draft": "Draf", + "paused": "Dijeda", + "sold_out": "Habis terjual", + "expired": "Kedaluwarsa", + "removed": "Dihapus", + "earthen": "Tanah", + "lined": "Berlapis", + "tank": "Tangki", + "cage": "Keramba", + "raceway": "Raceway", + "other": "Lainnya" + } +} diff --git a/frontend/src/components/Api_keys/TableApi_keys.tsx b/frontend/src/components/Api_keys/TableApi_keys.tsx index 0e2f3dc..9a36879 100644 --- a/frontend/src/components/Api_keys/TableApi_keys.tsx +++ b/frontend/src/components/Api_keys/TableApi_keys.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureApi_keysCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleApi_keys = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleApi_keys = ({ filterItems, setFilterItems, filters, showGrid }) `api_keys`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleApi_keys = ({ filterItems, setFilterItems, filters, showGrid }) return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleApi_keys = ({ filterItems, setFilterItems, filters, showGrid }) )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleApi_keys = ({ filterItems, setFilterItems, filters, showGrid }) )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Api_keys/configureApi_keysCols.tsx b/frontend/src/components/Api_keys/configureApi_keysCols.tsx index 10ae215..95e0c7b 100644 --- a/frontend/src/components/Api_keys/configureApi_keysCols.tsx +++ b/frontend/src/components/Api_keys/configureApi_keysCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'key_name', - headerName: 'KeyName', + headerName: translateCrudLabel('KeyName'), flex: 1, minWidth: 120, filterable: false, @@ -80,7 +81,7 @@ export const loadColumns = async ( { field: 'key_prefix', - headerName: 'KeyPrefix', + headerName: translateCrudLabel('KeyPrefix'), flex: 1, minWidth: 120, filterable: false, @@ -95,7 +96,7 @@ export const loadColumns = async ( { field: 'access_level', - headerName: 'AccessLevel', + headerName: translateCrudLabel('AccessLevel'), flex: 1, minWidth: 120, filterable: false, @@ -110,7 +111,7 @@ export const loadColumns = async ( { field: 'last_used_at', - headerName: 'LastUsedAt', + headerName: translateCrudLabel('LastUsedAt'), flex: 1, minWidth: 120, filterable: false, @@ -128,7 +129,7 @@ export const loadColumns = async ( { field: 'expires_at', - headerName: 'ExpiresAt', + headerName: translateCrudLabel('ExpiresAt'), flex: 1, minWidth: 120, filterable: false, @@ -146,7 +147,7 @@ export const loadColumns = async ( { field: 'is_revoked', - headerName: 'IsRevoked', + headerName: translateCrudLabel('IsRevoked'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/AsideMenuItem.tsx b/frontend/src/components/AsideMenuItem.tsx index dbb09b2..7da99e5 100644 --- a/frontend/src/components/AsideMenuItem.tsx +++ b/frontend/src/components/AsideMenuItem.tsx @@ -7,6 +7,8 @@ import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' import { useAppSelector } from '../stores/hooks' import { useRouter } from 'next/router' +import { useTranslation } from 'react-i18next' +import { translateCrudLabel } from '../helpers/translateCrudLabel' type Props = { item: MenuAsideItem @@ -14,6 +16,7 @@ type Props = { } const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { + const { t } = useTranslation('common') const [isLinkActive, setIsLinkActive] = useState(false) const [isDropdownActive, setIsDropdownActive] = useState(false) @@ -50,7 +53,7 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { item.menu ? '' : 'pr-12' } ${activeClassAddon}`} > - {item.label} + {translateCrudLabel(item.label, t)} {item.menu && ( state.style.corners); const asideStyle = useAppSelector((state) => state.style.asideStyle) const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle) @@ -67,7 +69,7 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props >
- Aquaculture Ops CRUD + {t('app.title')} {organizationName &&

{organizationName}

} diff --git a/frontend/src/components/Audit_logs/TableAudit_logs.tsx b/frontend/src/components/Audit_logs/TableAudit_logs.tsx index 389fe92..c25f7e7 100644 --- a/frontend/src/components/Audit_logs/TableAudit_logs.tsx +++ b/frontend/src/components/Audit_logs/TableAudit_logs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureAudit_logsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleAudit_logs = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleAudit_logs = ({ filterItems, setFilterItems, filters, showGrid `audit_logs`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleAudit_logs = ({ filterItems, setFilterItems, filters, showGrid return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleAudit_logs = ({ filterItems, setFilterItems, filters, showGrid )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleAudit_logs = ({ filterItems, setFilterItems, filters, showGrid )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Audit_logs/configureAudit_logsCols.tsx b/frontend/src/components/Audit_logs/configureAudit_logsCols.tsx index 0abdb60..e768db0 100644 --- a/frontend/src/components/Audit_logs/configureAudit_logsCols.tsx +++ b/frontend/src/components/Audit_logs/configureAudit_logsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'actor_user', - headerName: 'ActorUser', + headerName: translateCrudLabel('ActorUser'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'action', - headerName: 'Action', + headerName: translateCrudLabel('Action'), flex: 1, minWidth: 120, filterable: false, @@ -102,7 +103,7 @@ export const loadColumns = async ( { field: 'entity_name', - headerName: 'EntityName', + headerName: translateCrudLabel('EntityName'), flex: 1, minWidth: 120, filterable: false, @@ -117,7 +118,7 @@ export const loadColumns = async ( { field: 'entity_reference', - headerName: 'EntityReference', + headerName: translateCrudLabel('EntityReference'), flex: 1, minWidth: 120, filterable: false, @@ -132,7 +133,7 @@ export const loadColumns = async ( { field: 'occurred_at', - headerName: 'OccurredAt', + headerName: translateCrudLabel('OccurredAt'), flex: 1, minWidth: 120, filterable: false, @@ -150,7 +151,7 @@ export const loadColumns = async ( { field: 'ip_address', - headerName: 'IPAddress', + headerName: translateCrudLabel('IPAddress'), flex: 1, minWidth: 120, filterable: false, @@ -165,7 +166,7 @@ export const loadColumns = async ( { field: 'user_agent', - headerName: 'UserAgent', + headerName: translateCrudLabel('UserAgent'), flex: 1, minWidth: 120, filterable: false, @@ -180,7 +181,7 @@ export const loadColumns = async ( { field: 'change_summary', - headerName: 'ChangeSummary', + headerName: translateCrudLabel('ChangeSummary'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/BaseButton.tsx b/frontend/src/components/BaseButton.tsx index a137dc2..fa02515 100644 --- a/frontend/src/components/BaseButton.tsx +++ b/frontend/src/components/BaseButton.tsx @@ -4,6 +4,8 @@ import { getButtonColor } from '../colors' import BaseIcon from './BaseIcon' import type { ColorButtonKey } from '../interfaces' import { useAppSelector } from '../stores/hooks'; +import { useTranslation } from 'react-i18next'; +import { translateCrudLabel } from '../helpers/translateCrudLabel'; type Props = { label?: string @@ -42,7 +44,9 @@ export default function BaseButton({ roundedFull = false, onClick, }: Props) { + const { t } = useTranslation('common'); const corners = useAppSelector((state) => state.style.corners); + const translatedLabel = label ? translateCrudLabel(label, t) : ''; const componentClass = [ 'inline-flex', 'justify-center', @@ -76,7 +80,7 @@ export default function BaseButton({ const componentChildren = ( <> {icon && } - {label && {label}} + {label && {translatedLabel}} ) diff --git a/frontend/src/components/Batches/TableBatches.tsx b/frontend/src/components/Batches/TableBatches.tsx index 6e7ae9c..dcaca31 100644 --- a/frontend/src/components/Batches/TableBatches.tsx +++ b/frontend/src/components/Batches/TableBatches.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -20,11 +21,13 @@ import {dataGridStyles} from "../../styles"; import KanbanBoard from '../KanbanBoard/KanbanBoard'; import axios from 'axios'; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleBatches = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -213,7 +216,7 @@ const TableSampleBatches = ({ filterItems, setFilterItems, filters, showGrid }) `batches`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -310,7 +313,7 @@ const TableSampleBatches = ({ filterItems, setFilterItems, filters, showGrid }) return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -334,7 +337,7 @@ const TableSampleBatches = ({ filterItems, setFilterItems, filters, showGrid }) )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -359,22 +362,22 @@ const TableSampleBatches = ({ filterItems, setFilterItems, filters, showGrid }) )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Batches/configureBatchesCols.tsx b/frontend/src/components/Batches/configureBatchesCols.tsx index 5349dba..385ec72 100644 --- a/frontend/src/components/Batches/configureBatchesCols.tsx +++ b/frontend/src/components/Batches/configureBatchesCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'pond', - headerName: 'Pond', + headerName: translateCrudLabel('Pond'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'species', - headerName: 'Species', + headerName: translateCrudLabel('Species'), flex: 1, minWidth: 120, filterable: false, @@ -109,7 +110,7 @@ export const loadColumns = async ( { field: 'batch_code', - headerName: 'BatchCode', + headerName: translateCrudLabel('BatchCode'), flex: 1, minWidth: 120, filterable: false, @@ -124,7 +125,7 @@ export const loadColumns = async ( { field: 'stocked_at', - headerName: 'StockedAt', + headerName: translateCrudLabel('StockedAt'), flex: 1, minWidth: 120, filterable: false, @@ -142,7 +143,7 @@ export const loadColumns = async ( { field: 'initial_count', - headerName: 'InitialCount', + headerName: translateCrudLabel('InitialCount'), flex: 1, minWidth: 120, filterable: false, @@ -158,7 +159,7 @@ export const loadColumns = async ( { field: 'initial_avg_weight_g', - headerName: 'InitialAverageWeightG', + headerName: translateCrudLabel('InitialAverageWeightG'), flex: 1, minWidth: 120, filterable: false, @@ -174,7 +175,7 @@ export const loadColumns = async ( { field: 'batch_status', - headerName: 'BatchStatus', + headerName: translateCrudLabel('BatchStatus'), flex: 1, minWidth: 120, filterable: false, @@ -189,7 +190,7 @@ export const loadColumns = async ( { field: 'expected_harvest_at', - headerName: 'ExpectedHarvestAt', + headerName: translateCrudLabel('ExpectedHarvestAt'), flex: 1, minWidth: 120, filterable: false, @@ -207,7 +208,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/CardBoxComponentTitle.tsx b/frontend/src/components/CardBoxComponentTitle.tsx index 4f92def..94eb80e 100644 --- a/frontend/src/components/CardBoxComponentTitle.tsx +++ b/frontend/src/components/CardBoxComponentTitle.tsx @@ -1,4 +1,6 @@ import React, { ReactNode } from 'react' +import { useTranslation } from 'react-i18next' +import { translateCrudLabel } from '../helpers/translateCrudLabel' type Props = { title: string @@ -6,9 +8,11 @@ type Props = { } const CardBoxComponentTitle = ({ title, children }: Props) => { + const { t } = useTranslation('common') + return (
-

{title}

+

{translateCrudLabel(title, t)}

{children}
) diff --git a/frontend/src/components/Csv_jobs/TableCsv_jobs.tsx b/frontend/src/components/Csv_jobs/TableCsv_jobs.tsx index 6cece07..73c801c 100644 --- a/frontend/src/components/Csv_jobs/TableCsv_jobs.tsx +++ b/frontend/src/components/Csv_jobs/TableCsv_jobs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -20,11 +21,13 @@ import {dataGridStyles} from "../../styles"; import KanbanBoard from '../KanbanBoard/KanbanBoard'; import axios from 'axios'; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleCsv_jobs = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -211,7 +214,7 @@ const TableSampleCsv_jobs = ({ filterItems, setFilterItems, filters, showGrid }) `csv_jobs`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -308,7 +311,7 @@ const TableSampleCsv_jobs = ({ filterItems, setFilterItems, filters, showGrid }) return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -332,7 +335,7 @@ const TableSampleCsv_jobs = ({ filterItems, setFilterItems, filters, showGrid }) )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -357,22 +360,22 @@ const TableSampleCsv_jobs = ({ filterItems, setFilterItems, filters, showGrid }) )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Csv_jobs/configureCsv_jobsCols.tsx b/frontend/src/components/Csv_jobs/configureCsv_jobsCols.tsx index 7f83d63..449b257 100644 --- a/frontend/src/components/Csv_jobs/configureCsv_jobsCols.tsx +++ b/frontend/src/components/Csv_jobs/configureCsv_jobsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'job_type', - headerName: 'JobType', + headerName: translateCrudLabel('JobType'), flex: 1, minWidth: 120, filterable: false, @@ -80,7 +81,7 @@ export const loadColumns = async ( { field: 'entity_name', - headerName: 'EntityName', + headerName: translateCrudLabel('EntityName'), flex: 1, minWidth: 120, filterable: false, @@ -95,7 +96,7 @@ export const loadColumns = async ( { field: 'job_status', - headerName: 'JobStatus', + headerName: translateCrudLabel('JobStatus'), flex: 1, minWidth: 120, filterable: false, @@ -110,7 +111,7 @@ export const loadColumns = async ( { field: 'source_file', - headerName: 'SourceFile', + headerName: translateCrudLabel('SourceFile'), flex: 1, minWidth: 120, filterable: false, @@ -136,7 +137,7 @@ export const loadColumns = async ( { field: 'result_file', - headerName: 'ResultFile', + headerName: translateCrudLabel('ResultFile'), flex: 1, minWidth: 120, filterable: false, @@ -162,7 +163,7 @@ export const loadColumns = async ( { field: 'total_rows', - headerName: 'TotalRows', + headerName: translateCrudLabel('TotalRows'), flex: 1, minWidth: 120, filterable: false, @@ -178,7 +179,7 @@ export const loadColumns = async ( { field: 'processed_rows', - headerName: 'ProcessedRows', + headerName: translateCrudLabel('ProcessedRows'), flex: 1, minWidth: 120, filterable: false, @@ -194,7 +195,7 @@ export const loadColumns = async ( { field: 'error_rows', - headerName: 'ErrorRows', + headerName: translateCrudLabel('ErrorRows'), flex: 1, minWidth: 120, filterable: false, @@ -210,7 +211,7 @@ export const loadColumns = async ( { field: 'error_report', - headerName: 'ErrorReport', + headerName: translateCrudLabel('ErrorReport'), flex: 1, minWidth: 120, filterable: false, @@ -225,7 +226,7 @@ export const loadColumns = async ( { field: 'requested_by', - headerName: 'RequestedBy', + headerName: translateCrudLabel('RequestedBy'), flex: 1, minWidth: 120, filterable: false, @@ -247,7 +248,7 @@ export const loadColumns = async ( { field: 'started_at', - headerName: 'StartedAt', + headerName: translateCrudLabel('StartedAt'), flex: 1, minWidth: 120, filterable: false, @@ -265,7 +266,7 @@ export const loadColumns = async ( { field: 'finished_at', - headerName: 'FinishedAt', + headerName: translateCrudLabel('FinishedAt'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/DragDropFilePicker.tsx b/frontend/src/components/DragDropFilePicker.tsx index 821570d..9bfa0b2 100644 --- a/frontend/src/components/DragDropFilePicker.tsx +++ b/frontend/src/components/DragDropFilePicker.tsx @@ -1,6 +1,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import BaseIcon from './BaseIcon'; import { mdiFileUploadOutline } from '@mdi/js'; +import { useTranslation } from 'react-i18next'; type Props = { file: File | null; @@ -9,6 +10,7 @@ type Props = { }; const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { + const { t } = useTranslation('common'); const [highlight, setHighlight] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const fileInput = React.createRef(); @@ -26,7 +28,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { setFile(newFile); setErrorMessage(''); } else { - setErrorMessage(`Allowed formats: ${formats}`); + setErrorMessage(t('labels.allowed_formats', { formats })); } } } @@ -97,8 +99,7 @@ const DragDropFilePicker = ({ file, setFile, formats = '' }: Props) => { ) : ( <>

- Click to upload or drag - and drop + {t('labels.click_to_upload')} {t('labels.or_drag_and_drop')}

{formats && (

diff --git a/frontend/src/components/Email_notifications/TableEmail_notifications.tsx b/frontend/src/components/Email_notifications/TableEmail_notifications.tsx index ce0637c..0c1b020 100644 --- a/frontend/src/components/Email_notifications/TableEmail_notifications.tsx +++ b/frontend/src/components/Email_notifications/TableEmail_notifications.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureEmail_notificationsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleEmail_notifications = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleEmail_notifications = ({ filterItems, setFilterItems, filters, `email_notifications`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleEmail_notifications = ({ filterItems, setFilterItems, filters, return (

-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleEmail_notifications = ({ filterItems, setFilterItems, filters, )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleEmail_notifications = ({ filterItems, setFilterItems, filters, )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Email_notifications/configureEmail_notificationsCols.tsx b/frontend/src/components/Email_notifications/configureEmail_notificationsCols.tsx index 8007fb6..bc94751 100644 --- a/frontend/src/components/Email_notifications/configureEmail_notificationsCols.tsx +++ b/frontend/src/components/Email_notifications/configureEmail_notificationsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'recipient_user', - headerName: 'RecipientUser', + headerName: translateCrudLabel('RecipientUser'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'to_email', - headerName: 'ToEmail', + headerName: translateCrudLabel('ToEmail'), flex: 1, minWidth: 120, filterable: false, @@ -102,7 +103,7 @@ export const loadColumns = async ( { field: 'subject', - headerName: 'Subject', + headerName: translateCrudLabel('Subject'), flex: 1, minWidth: 120, filterable: false, @@ -117,7 +118,7 @@ export const loadColumns = async ( { field: 'template_key', - headerName: 'TemplateKey', + headerName: translateCrudLabel('TemplateKey'), flex: 1, minWidth: 120, filterable: false, @@ -132,7 +133,7 @@ export const loadColumns = async ( { field: 'payload', - headerName: 'Payload', + headerName: translateCrudLabel('Payload'), flex: 1, minWidth: 120, filterable: false, @@ -147,7 +148,7 @@ export const loadColumns = async ( { field: 'send_status', - headerName: 'SendStatus', + headerName: translateCrudLabel('SendStatus'), flex: 1, minWidth: 120, filterable: false, @@ -162,7 +163,7 @@ export const loadColumns = async ( { field: 'sent_at', - headerName: 'SentAt', + headerName: translateCrudLabel('SentAt'), flex: 1, minWidth: 120, filterable: false, @@ -180,7 +181,7 @@ export const loadColumns = async ( { field: 'failure_reason', - headerName: 'FailureReason', + headerName: translateCrudLabel('FailureReason'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Feed_products/TableFeed_products.tsx b/frontend/src/components/Feed_products/TableFeed_products.tsx index b58c816..eff7b38 100644 --- a/frontend/src/components/Feed_products/TableFeed_products.tsx +++ b/frontend/src/components/Feed_products/TableFeed_products.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureFeed_productsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleFeed_products = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleFeed_products = ({ filterItems, setFilterItems, filters, showGr `feed_products`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleFeed_products = ({ filterItems, setFilterItems, filters, showGr return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleFeed_products = ({ filterItems, setFilterItems, filters, showGr )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleFeed_products = ({ filterItems, setFilterItems, filters, showGr )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Feed_products/configureFeed_productsCols.tsx b/frontend/src/components/Feed_products/configureFeed_productsCols.tsx index ff1a2b0..6db9d5f 100644 --- a/frontend/src/components/Feed_products/configureFeed_productsCols.tsx +++ b/frontend/src/components/Feed_products/configureFeed_productsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'product_name', - headerName: 'ProductName', + headerName: translateCrudLabel('ProductName'), flex: 1, minWidth: 120, filterable: false, @@ -80,7 +81,7 @@ export const loadColumns = async ( { field: 'brand', - headerName: 'Brand', + headerName: translateCrudLabel('Brand'), flex: 1, minWidth: 120, filterable: false, @@ -95,7 +96,7 @@ export const loadColumns = async ( { field: 'feed_type', - headerName: 'FeedType', + headerName: translateCrudLabel('FeedType'), flex: 1, minWidth: 120, filterable: false, @@ -110,7 +111,7 @@ export const loadColumns = async ( { field: 'protein_percent', - headerName: 'ProteinPercent', + headerName: translateCrudLabel('ProteinPercent'), flex: 1, minWidth: 120, filterable: false, @@ -126,7 +127,7 @@ export const loadColumns = async ( { field: 'fat_percent', - headerName: 'FatPercent', + headerName: translateCrudLabel('FatPercent'), flex: 1, minWidth: 120, filterable: false, @@ -142,7 +143,7 @@ export const loadColumns = async ( { field: 'pellet_size_mm', - headerName: 'PelletSizeMm', + headerName: translateCrudLabel('PelletSizeMm'), flex: 1, minWidth: 120, filterable: false, @@ -158,7 +159,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Feeding_logs/TableFeeding_logs.tsx b/frontend/src/components/Feeding_logs/TableFeeding_logs.tsx index f90fae2..2a17c81 100644 --- a/frontend/src/components/Feeding_logs/TableFeeding_logs.tsx +++ b/frontend/src/components/Feeding_logs/TableFeeding_logs.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,6 +17,7 @@ import {loadColumns} from "./configureFeeding_logsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; import BigCalendar from "../BigCalendar"; @@ -25,6 +27,7 @@ import { SlotInfo } from 'react-big-calendar'; const perPage = 100 const TableSampleFeeding_logs = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -183,7 +186,7 @@ const TableSampleFeeding_logs = ({ filterItems, setFilterItems, filters, showGri `feeding_logs`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -280,7 +283,7 @@ const TableSampleFeeding_logs = ({ filterItems, setFilterItems, filters, showGri return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -304,7 +307,7 @@ const TableSampleFeeding_logs = ({ filterItems, setFilterItems, filters, showGri )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -329,22 +332,22 @@ const TableSampleFeeding_logs = ({ filterItems, setFilterItems, filters, showGri )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Feeding_logs/configureFeeding_logsCols.tsx b/frontend/src/components/Feeding_logs/configureFeeding_logsCols.tsx index 6000804..bc242e3 100644 --- a/frontend/src/components/Feeding_logs/configureFeeding_logsCols.tsx +++ b/frontend/src/components/Feeding_logs/configureFeeding_logsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'batch', - headerName: 'Batch', + headerName: translateCrudLabel('Batch'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'feed_product', - headerName: 'FeedProduct', + headerName: translateCrudLabel('FeedProduct'), flex: 1, minWidth: 120, filterable: false, @@ -109,7 +110,7 @@ export const loadColumns = async ( { field: 'fed_at', - headerName: 'FedAt', + headerName: translateCrudLabel('FedAt'), flex: 1, minWidth: 120, filterable: false, @@ -127,7 +128,7 @@ export const loadColumns = async ( { field: 'quantity_kg', - headerName: 'QuantityKg', + headerName: translateCrudLabel('QuantityKg'), flex: 1, minWidth: 120, filterable: false, @@ -143,7 +144,7 @@ export const loadColumns = async ( { field: 'feeding_method', - headerName: 'FeedingMethod', + headerName: translateCrudLabel('FeedingMethod'), flex: 1, minWidth: 120, filterable: false, @@ -158,7 +159,7 @@ export const loadColumns = async ( { field: 'appetite', - headerName: 'Appetite', + headerName: translateCrudLabel('Appetite'), flex: 1, minWidth: 120, filterable: false, @@ -173,7 +174,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, @@ -188,7 +189,7 @@ export const loadColumns = async ( { field: 'recorded_by', - headerName: 'RecordedBy', + headerName: translateCrudLabel('RecordedBy'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/FormCheckRadio.tsx b/frontend/src/components/FormCheckRadio.tsx index 7a9bc31..e610b4c 100644 --- a/frontend/src/components/FormCheckRadio.tsx +++ b/frontend/src/components/FormCheckRadio.tsx @@ -1,4 +1,6 @@ import { ReactNode } from 'react' +import { useTranslation } from 'react-i18next' +import { translateCrudLabel } from '../helpers/translateCrudLabel' type Props = { children: ReactNode @@ -8,11 +10,13 @@ type Props = { } const FormCheckRadio = (props: Props) => { + const { t } = useTranslation('common') + return ( ) } diff --git a/frontend/src/components/FormField.tsx b/frontend/src/components/FormField.tsx index 988ac39..16894ee 100644 --- a/frontend/src/components/FormField.tsx +++ b/frontend/src/components/FormField.tsx @@ -1,6 +1,8 @@ import { Children, cloneElement, ReactElement, ReactNode } from 'react' import BaseIcon from './BaseIcon' import { useAppSelector } from '../stores/hooks'; +import { useTranslation } from 'react-i18next'; +import { translateCrudLabel } from '../helpers/translateCrudLabel'; type Props = { label?: string @@ -18,6 +20,7 @@ type Props = { } const FormField = ({ icons = [], ...props }: Props) => { + const { t } = useTranslation('common') const childrenCount = Children.count(props.children) const bgColor = useAppSelector((state) => state.style.cardsColor); const focusRing = useAppSelector((state) => state.style.focusRingColor); @@ -50,7 +53,7 @@ const FormField = ({ icons = [], ...props }: Props) => { htmlFor={props.labelFor} className={`block font-bold mb-2 ${props.labelFor ? 'cursor-pointer' : ''}`} > - {props.label} + {translateCrudLabel(props.label, t)} )}
@@ -71,7 +74,7 @@ const FormField = ({ icons = [], ...props }: Props) => { ))}
{props.help && ( -
{props.help}
+
{translateCrudLabel(props.help, t)}
)}
) diff --git a/frontend/src/components/Harvests/TableHarvests.tsx b/frontend/src/components/Harvests/TableHarvests.tsx index 0a04d88..10ce1ae 100644 --- a/frontend/src/components/Harvests/TableHarvests.tsx +++ b/frontend/src/components/Harvests/TableHarvests.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureHarvestsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleHarvests = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleHarvests = ({ filterItems, setFilterItems, filters, showGrid }) `harvests`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleHarvests = ({ filterItems, setFilterItems, filters, showGrid }) return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleHarvests = ({ filterItems, setFilterItems, filters, showGrid }) )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleHarvests = ({ filterItems, setFilterItems, filters, showGrid }) )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Harvests/configureHarvestsCols.tsx b/frontend/src/components/Harvests/configureHarvestsCols.tsx index d5902d6..40a6407 100644 --- a/frontend/src/components/Harvests/configureHarvestsCols.tsx +++ b/frontend/src/components/Harvests/configureHarvestsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'batch', - headerName: 'Batch', + headerName: translateCrudLabel('Batch'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'harvested_at', - headerName: 'HarvestedAt', + headerName: translateCrudLabel('HarvestedAt'), flex: 1, minWidth: 120, filterable: false, @@ -105,7 +106,7 @@ export const loadColumns = async ( { field: 'total_weight_kg', - headerName: 'TotalWeightKg', + headerName: translateCrudLabel('TotalWeightKg'), flex: 1, minWidth: 120, filterable: false, @@ -121,7 +122,7 @@ export const loadColumns = async ( { field: 'total_count', - headerName: 'TotalCount', + headerName: translateCrudLabel('TotalCount'), flex: 1, minWidth: 120, filterable: false, @@ -137,7 +138,7 @@ export const loadColumns = async ( { field: 'avg_weight_g', - headerName: 'AverageWeightG', + headerName: translateCrudLabel('AverageWeightG'), flex: 1, minWidth: 120, filterable: false, @@ -153,7 +154,7 @@ export const loadColumns = async ( { field: 'grade', - headerName: 'Grade', + headerName: translateCrudLabel('Grade'), flex: 1, minWidth: 120, filterable: false, @@ -168,7 +169,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, @@ -183,7 +184,7 @@ export const loadColumns = async ( { field: 'recorded_by', - headerName: 'RecordedBy', + headerName: translateCrudLabel('RecordedBy'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Health_events/TableHealth_events.tsx b/frontend/src/components/Health_events/TableHealth_events.tsx index 713ac2e..ed9996c 100644 --- a/frontend/src/components/Health_events/TableHealth_events.tsx +++ b/frontend/src/components/Health_events/TableHealth_events.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -20,11 +21,13 @@ import {dataGridStyles} from "../../styles"; import KanbanBoard from '../KanbanBoard/KanbanBoard'; import axios from 'axios'; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleHealth_events = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -211,7 +214,7 @@ const TableSampleHealth_events = ({ filterItems, setFilterItems, filters, showGr `health_events`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -308,7 +311,7 @@ const TableSampleHealth_events = ({ filterItems, setFilterItems, filters, showGr return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -332,7 +335,7 @@ const TableSampleHealth_events = ({ filterItems, setFilterItems, filters, showGr )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -357,22 +360,22 @@ const TableSampleHealth_events = ({ filterItems, setFilterItems, filters, showGr )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Health_events/configureHealth_eventsCols.tsx b/frontend/src/components/Health_events/configureHealth_eventsCols.tsx index b7756eb..532bc39 100644 --- a/frontend/src/components/Health_events/configureHealth_eventsCols.tsx +++ b/frontend/src/components/Health_events/configureHealth_eventsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'batch', - headerName: 'Batch', + headerName: translateCrudLabel('Batch'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'observed_at', - headerName: 'ObservedAt', + headerName: translateCrudLabel('ObservedAt'), flex: 1, minWidth: 120, filterable: false, @@ -105,7 +106,7 @@ export const loadColumns = async ( { field: 'event_type', - headerName: 'EventType', + headerName: translateCrudLabel('EventType'), flex: 1, minWidth: 120, filterable: false, @@ -120,7 +121,7 @@ export const loadColumns = async ( { field: 'severity', - headerName: 'Severity', + headerName: translateCrudLabel('Severity'), flex: 1, minWidth: 120, filterable: false, @@ -135,7 +136,7 @@ export const loadColumns = async ( { field: 'mortality_count', - headerName: 'MortalityCount', + headerName: translateCrudLabel('MortalityCount'), flex: 1, minWidth: 120, filterable: false, @@ -151,7 +152,7 @@ export const loadColumns = async ( { field: 'symptoms', - headerName: 'Symptoms', + headerName: translateCrudLabel('Symptoms'), flex: 1, minWidth: 120, filterable: false, @@ -166,7 +167,7 @@ export const loadColumns = async ( { field: 'treatment', - headerName: 'Treatment', + headerName: translateCrudLabel('Treatment'), flex: 1, minWidth: 120, filterable: false, @@ -181,7 +182,7 @@ export const loadColumns = async ( { field: 'attachments', - headerName: 'Attachments', + headerName: translateCrudLabel('Attachments'), flex: 1, minWidth: 120, filterable: false, @@ -207,7 +208,7 @@ export const loadColumns = async ( { field: 'reported_by', - headerName: 'ReportedBy', + headerName: translateCrudLabel('ReportedBy'), flex: 1, minWidth: 120, filterable: false, @@ -229,7 +230,7 @@ export const loadColumns = async ( { field: 'resolution_status', - headerName: 'ResolutionStatus', + headerName: translateCrudLabel('ResolutionStatus'), flex: 1, minWidth: 120, filterable: false, @@ -244,7 +245,7 @@ export const loadColumns = async ( { field: 'resolved_at', - headerName: 'ResolvedAt', + headerName: translateCrudLabel('ResolvedAt'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Inventory_items/TableInventory_items.tsx b/frontend/src/components/Inventory_items/TableInventory_items.tsx index 2c529d8..88acbf8 100644 --- a/frontend/src/components/Inventory_items/TableInventory_items.tsx +++ b/frontend/src/components/Inventory_items/TableInventory_items.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureInventory_itemsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleInventory_items = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleInventory_items = ({ filterItems, setFilterItems, filters, show `inventory_items`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleInventory_items = ({ filterItems, setFilterItems, filters, show return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleInventory_items = ({ filterItems, setFilterItems, filters, show )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleInventory_items = ({ filterItems, setFilterItems, filters, show )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Inventory_items/configureInventory_itemsCols.tsx b/frontend/src/components/Inventory_items/configureInventory_itemsCols.tsx index e739ae4..64d5078 100644 --- a/frontend/src/components/Inventory_items/configureInventory_itemsCols.tsx +++ b/frontend/src/components/Inventory_items/configureInventory_itemsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'location', - headerName: 'Location', + headerName: translateCrudLabel('Location'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'item_category', - headerName: 'ItemCategory', + headerName: translateCrudLabel('ItemCategory'), flex: 1, minWidth: 120, filterable: false, @@ -102,7 +103,7 @@ export const loadColumns = async ( { field: 'item_name', - headerName: 'ItemName', + headerName: translateCrudLabel('ItemName'), flex: 1, minWidth: 120, filterable: false, @@ -117,7 +118,7 @@ export const loadColumns = async ( { field: 'sku', - headerName: 'SKU', + headerName: translateCrudLabel('SKU'), flex: 1, minWidth: 120, filterable: false, @@ -132,7 +133,7 @@ export const loadColumns = async ( { field: 'unit', - headerName: 'Unit', + headerName: translateCrudLabel('Unit'), flex: 1, minWidth: 120, filterable: false, @@ -147,7 +148,7 @@ export const loadColumns = async ( { field: 'quantity_on_hand', - headerName: 'QuantityOnHand', + headerName: translateCrudLabel('QuantityOnHand'), flex: 1, minWidth: 120, filterable: false, @@ -163,7 +164,7 @@ export const loadColumns = async ( { field: 'reorder_level', - headerName: 'ReorderLevel', + headerName: translateCrudLabel('ReorderLevel'), flex: 1, minWidth: 120, filterable: false, @@ -179,7 +180,7 @@ export const loadColumns = async ( { field: 'unit_cost', - headerName: 'UnitCost', + headerName: translateCrudLabel('UnitCost'), flex: 1, minWidth: 120, filterable: false, @@ -195,7 +196,7 @@ export const loadColumns = async ( { field: 'feed_product', - headerName: 'FeedProduct', + headerName: translateCrudLabel('FeedProduct'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Inventory_movements/TableInventory_movements.tsx b/frontend/src/components/Inventory_movements/TableInventory_movements.tsx index e526e1f..069e865 100644 --- a/frontend/src/components/Inventory_movements/TableInventory_movements.tsx +++ b/frontend/src/components/Inventory_movements/TableInventory_movements.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -16,12 +17,14 @@ import {loadColumns} from "./configureInventory_movementsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -180,7 +183,7 @@ const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, `inventory_movements`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -277,7 +280,7 @@ const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -301,7 +304,7 @@ const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -326,22 +329,22 @@ const TableSampleInventory_movements = ({ filterItems, setFilterItems, filters, )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Inventory_movements/configureInventory_movementsCols.tsx b/frontend/src/components/Inventory_movements/configureInventory_movementsCols.tsx index 7516efd..bb44948 100644 --- a/frontend/src/components/Inventory_movements/configureInventory_movementsCols.tsx +++ b/frontend/src/components/Inventory_movements/configureInventory_movementsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'tenant', - headerName: 'Tenant', + headerName: translateCrudLabel('Tenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'inventory_item', - headerName: 'InventoryItem', + headerName: translateCrudLabel('InventoryItem'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'movement_type', - headerName: 'MovementType', + headerName: translateCrudLabel('MovementType'), flex: 1, minWidth: 120, filterable: false, @@ -102,7 +103,7 @@ export const loadColumns = async ( { field: 'quantity', - headerName: 'Quantity', + headerName: translateCrudLabel('Quantity'), flex: 1, minWidth: 120, filterable: false, @@ -118,7 +119,7 @@ export const loadColumns = async ( { field: 'moved_at', - headerName: 'MovedAt', + headerName: translateCrudLabel('MovedAt'), flex: 1, minWidth: 120, filterable: false, @@ -136,7 +137,7 @@ export const loadColumns = async ( { field: 'batch', - headerName: 'Batch', + headerName: translateCrudLabel('Batch'), flex: 1, minWidth: 120, filterable: false, @@ -158,7 +159,7 @@ export const loadColumns = async ( { field: 'reference', - headerName: 'Reference', + headerName: translateCrudLabel('Reference'), flex: 1, minWidth: 120, filterable: false, @@ -173,7 +174,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/Investments/TableInvestments.tsx b/frontend/src/components/Investments/TableInvestments.tsx index 7cb85cd..2a19f29 100644 --- a/frontend/src/components/Investments/TableInvestments.tsx +++ b/frontend/src/components/Investments/TableInvestments.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState, useMemo } from 'react' +import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom'; import { ToastContainer, toast } from 'react-toastify'; import BaseButton from '../BaseButton' @@ -20,11 +21,13 @@ import {dataGridStyles} from "../../styles"; import KanbanBoard from '../KanbanBoard/KanbanBoard'; import axios from 'axios'; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; const perPage = 10 const TableSampleInvestments = ({ filterItems, setFilterItems, filters, showGrid }) => { + const { t, i18n } = useTranslation('common'); const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -211,7 +214,7 @@ const TableSampleInvestments = ({ filterItems, setFilterItems, filters, showGrid `investments`, currentUser, ).then((newCols) => setColumns(newCols)); - }, [currentUser]); + }, [currentUser, i18n.language]); @@ -308,7 +311,7 @@ const TableSampleInvestments = ({ filterItems, setFilterItems, filters, showGrid return (
-
Filter
+
{translateCrudLabel('Filter', t)}
- {selectOption.label} + {translateCrudLabel(selectOption.label, t)} ))} @@ -332,7 +335,7 @@ const TableSampleInvestments = ({ filterItems, setFilterItems, filters, showGrid )?.type === 'enum' ? (
- Value + {translateCrudLabel('Value', t)}
- + {filters.find((filter) => filter.title === filterItem?.fields?.selectedField )?.options?.map((option) => ( ))} @@ -357,22 +360,22 @@ const TableSampleInvestments = ({ filterItems, setFilterItems, filters, showGrid )?.number ? (
-
From
+
{translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
- From + {translateCrudLabel('From', t)}
-
To
+
{translateCrudLabel('To', t)}
) : (
-
Contains
+
{translateCrudLabel('Contains', t)}
)}
-
Action
+
{translateCrudLabel('Action', t)}
-

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

diff --git a/frontend/src/components/Investments/configureInvestmentsCols.tsx b/frontend/src/components/Investments/configureInvestmentsCols.tsx index 606d8a9..9fd4404 100644 --- a/frontend/src/components/Investments/configureInvestmentsCols.tsx +++ b/frontend/src/components/Investments/configureInvestmentsCols.tsx @@ -14,6 +14,7 @@ import DataGridMultiSelect from "../DataGridMultiSelect"; import ListActionsPopover from '../ListActionsPopover'; import {hasPermission} from "../../helpers/userPermissions"; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Params = (id: string) => void; @@ -43,7 +44,7 @@ export const loadColumns = async ( { field: 'investor_tenant', - headerName: 'InvestorTenant', + headerName: translateCrudLabel('InvestorTenant'), flex: 1, minWidth: 120, filterable: false, @@ -65,7 +66,7 @@ export const loadColumns = async ( { field: 'farm_tenant', - headerName: 'FarmTenant', + headerName: translateCrudLabel('FarmTenant'), flex: 1, minWidth: 120, filterable: false, @@ -87,7 +88,7 @@ export const loadColumns = async ( { field: 'investment_name', - headerName: 'InvestmentName', + headerName: translateCrudLabel('InvestmentName'), flex: 1, minWidth: 120, filterable: false, @@ -102,7 +103,7 @@ export const loadColumns = async ( { field: 'instrument_type', - headerName: 'InstrumentType', + headerName: translateCrudLabel('InstrumentType'), flex: 1, minWidth: 120, filterable: false, @@ -117,7 +118,7 @@ export const loadColumns = async ( { field: 'amount_committed', - headerName: 'AmountCommitted', + headerName: translateCrudLabel('AmountCommitted'), flex: 1, minWidth: 120, filterable: false, @@ -133,7 +134,7 @@ export const loadColumns = async ( { field: 'amount_funded', - headerName: 'AmountFunded', + headerName: translateCrudLabel('AmountFunded'), flex: 1, minWidth: 120, filterable: false, @@ -149,7 +150,7 @@ export const loadColumns = async ( { field: 'currency', - headerName: 'Currency', + headerName: translateCrudLabel('Currency'), flex: 1, minWidth: 120, filterable: false, @@ -164,7 +165,7 @@ export const loadColumns = async ( { field: 'committed_at', - headerName: 'CommittedAt', + headerName: translateCrudLabel('CommittedAt'), flex: 1, minWidth: 120, filterable: false, @@ -182,7 +183,7 @@ export const loadColumns = async ( { field: 'funded_at', - headerName: 'FundedAt', + headerName: translateCrudLabel('FundedAt'), flex: 1, minWidth: 120, filterable: false, @@ -200,7 +201,7 @@ export const loadColumns = async ( { field: 'investment_status', - headerName: 'InvestmentStatus', + headerName: translateCrudLabel('InvestmentStatus'), flex: 1, minWidth: 120, filterable: false, @@ -215,7 +216,7 @@ export const loadColumns = async ( { field: 'notes', - headerName: 'Notes', + headerName: translateCrudLabel('Notes'), flex: 1, minWidth: 120, filterable: false, diff --git a/frontend/src/components/KanbanBoard/KanbanColumn.tsx b/frontend/src/components/KanbanBoard/KanbanColumn.tsx index 425a0d3..2bb975b 100644 --- a/frontend/src/components/KanbanBoard/KanbanColumn.tsx +++ b/frontend/src/components/KanbanBoard/KanbanColumn.tsx @@ -6,6 +6,8 @@ import CardBoxModal from '../CardBoxModal'; import { AsyncThunk } from '@reduxjs/toolkit'; import { useDrop } from 'react-dnd'; import KanbanCard from './KanbanCard'; +import { useTranslation } from 'react-i18next'; +import { translateCrudLabel } from '../../helpers/translateCrudLabel'; type Props = { column: { id: string; label: string }; @@ -33,6 +35,7 @@ const KanbanColumn = ({ deleteThunk, updateThunk, }: Props) => { + const { t, i18n } = useTranslation('common'); const [currentPage, setCurrentPage] = useState(0); const [count, setCount] = useState(0); const [data, setData] = useState(null); @@ -106,7 +109,7 @@ const KanbanColumn = ({ setLoading(false); }); }, - [currentUser, column], + [currentUser, column, i18n.language], ); useEffect(() => { @@ -165,7 +168,7 @@ const KanbanColumn = ({ } >
-

{column.label}

+

{translateCrudLabel(column.label, t)}

{count}

))} {!data?.length && ( -

No data

+

{translateCrudLabel('No data', t)}

)}
@@ -200,7 +203,7 @@ const KanbanColumn = ({ onConfirm={onDeleteConfirm} onCancel={() => setItemIdToDelete('')} > -

Are you sure you want to delete this item?

+

{translateCrudLabel('Are you sure you want to delete this item?', t)}

); diff --git a/frontend/src/components/LanguageSwitcher.tsx b/frontend/src/components/LanguageSwitcher.tsx index f2f373a..75a8c65 100644 --- a/frontend/src/components/LanguageSwitcher.tsx +++ b/frontend/src/components/LanguageSwitcher.tsx @@ -1,15 +1,17 @@ import React, { useEffect, useState } from 'react'; import Select, { components, SingleValueProps, OptionProps } from 'react-select'; +import { useTranslation } from 'react-i18next'; type LanguageOption = { label: string; value: string }; const LANGS: LanguageOption[] = [ - { value: 'en', label: '🇬🇧 EN' }, - { value: 'fr', label: '🇫🇷 FR' }, - { value: 'es', label: '🇪🇸 ES' }, - { value: 'de', label: '🇩🇪 DE' }, + { value: 'id', label: '🇮🇩 ID' }, + { value: 'en-GB', label: '🇬🇧 EN-UK' }, ]; +const getLanguageOption = (language?: string) => + LANGS.find((option) => option.value === language) || LANGS[0]; + const Option = (props: OptionProps) => ( {props.data.label} @@ -23,22 +25,42 @@ const SingleVal = (props: SingleValueProps) => ( ); const LanguageSwitcher: React.FC = () => { + const { i18n } = useTranslation('common'); const [mounted, setMounted] = useState(false); const [selected, setSelected] = useState(LANGS[0]); useEffect(() => { setMounted(true); - }, []); - const handleChange = (opt: LanguageOption | null) => { + const savedLanguage = typeof window !== 'undefined' ? window.localStorage.getItem('app_lang_') : null; + const selectedLanguage = getLanguageOption(savedLanguage || i18n.resolvedLanguage || i18n.language); + setSelected(selectedLanguage); + + if (selectedLanguage.value !== i18n.language) { + i18n.changeLanguage(selectedLanguage.value); + } + + const handleLanguageChanged = (language: string) => { + setSelected(getLanguageOption(language)); + }; + + i18n.on('languageChanged', handleLanguageChanged); + return () => { + i18n.off('languageChanged', handleLanguageChanged); + }; + }, [i18n]); + + const handleChange = async (opt: LanguageOption | null) => { if (!opt) return; setSelected(opt); + window.localStorage.setItem('app_lang_', opt.value); + await i18n.changeLanguage(opt.value); }; if (!mounted) return null; return ( -
+
{