django-cfg 1.3.11__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/inlines.py +11 -5
- django_cfg/apps/accounts/admin/user_admin.py +39 -16
- django_cfg/apps/accounts/serializers/profile.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +18 -11
- django_cfg/apps/accounts/signals.py +15 -24
- django_cfg/apps/accounts/utils/notifications.py +217 -358
- django_cfg/apps/accounts/views/otp.py +2 -2
- django_cfg/apps/accounts/views/webhook.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +1 -1
- django_cfg/apps/api/commands/views.py +66 -83
- django_cfg/apps/api/health/drf_views.py +269 -0
- django_cfg/apps/api/health/serializers.py +45 -0
- django_cfg/apps/api/health/urls.py +6 -1
- django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
- django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
- django_cfg/apps/knowbase/admin/document_admin.py +136 -270
- django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
- django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
- django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
- django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
- django_cfg/apps/knowbase/config/constance_fields.py +1 -1
- django_cfg/apps/knowbase/config/settings.py +2 -2
- django_cfg/apps/knowbase/mixins/__init__.py +19 -2
- django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
- django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
- django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
- django_cfg/apps/knowbase/mixins/creator.py +10 -10
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
- django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
- django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
- django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
- django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
- django_cfg/apps/knowbase/mixins/service.py +2 -2
- django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
- django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
- django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
- django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
- django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
- django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
- django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
- django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
- django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
- django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
- django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
- django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
- django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
- django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
- django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
- django_cfg/apps/knowbase/services/embedding/models.py +18 -14
- django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
- django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
- django_cfg/apps/leads/tests.py +1 -1
- django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
- django_cfg/apps/payments/admin/balance_admin.py +1 -1
- django_cfg/apps/payments/admin/currencies_admin.py +1 -1
- django_cfg/apps/payments/admin/payments_admin.py +1 -1
- django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
- django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
- django_cfg/apps/payments/admin_interface/views/base.py +29 -2
- django_cfg/apps/payments/apps.py +1 -1
- django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
- django_cfg/apps/payments/config/helpers.py +3 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
- django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
- django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
- django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
- django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
- django_cfg/apps/payments/management/commands/test_providers.py +1 -1
- django_cfg/apps/payments/middleware/api_access.py +1 -1
- django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
- django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
- django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
- django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
- django_cfg/apps/payments/services/core/base.py +1 -1
- django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
- django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
- django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
- django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
- django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
- django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
- django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
- django_cfg/apps/payments/services/core/payment_service.py +124 -612
- django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
- django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
- django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
- django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
- django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
- django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
- django_cfg/apps/payments/services/providers/base.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
- django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
- django_cfg/apps/payments/services/providers/registry.py +1 -1
- django_cfg/apps/payments/services/providers/sync_service.py +1 -1
- django_cfg/apps/payments/signals/__init__.py +1 -1
- django_cfg/apps/payments/signals/api_key_signals.py +1 -1
- django_cfg/apps/payments/signals/balance_signals.py +1 -1
- django_cfg/apps/payments/signals/payment_signals.py +1 -1
- django_cfg/apps/payments/signals/subscription_signals.py +1 -1
- django_cfg/apps/payments/views/api/api_keys.py +1 -1
- django_cfg/apps/payments/views/api/balances.py +1 -1
- django_cfg/apps/payments/views/api/base.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +1 -1
- django_cfg/apps/payments/views/api/payments.py +1 -1
- django_cfg/apps/payments/views/api/subscriptions.py +1 -1
- django_cfg/apps/payments/views/api/webhooks.py +1 -1
- django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
- django_cfg/apps/payments/views/serializers/balances.py +1 -1
- django_cfg/apps/payments/views/serializers/currencies.py +1 -1
- django_cfg/apps/payments/views/serializers/payments.py +1 -1
- django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
- django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
- django_cfg/apps/support/admin/support_admin.py +21 -13
- django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
- django_cfg/apps/support/utils/support_email_service.py +1 -1
- django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
- django_cfg/apps/tasks/utils/simulator.py +1 -1
- django_cfg/apps/tasks/views/dashboard.py +33 -3
- django_cfg/apps/urls.py +5 -1
- django_cfg/cli/README.md +57 -471
- django_cfg/cli/commands/create_project.py +140 -529
- django_cfg/cli/main.py +13 -10
- django_cfg/core/__init__.py +63 -6
- django_cfg/core/base/__init__.py +5 -0
- django_cfg/core/base/config_model.py +652 -0
- django_cfg/core/builders/__init__.py +11 -0
- django_cfg/core/builders/apps_builder.py +258 -0
- django_cfg/core/builders/middleware_builder.py +115 -0
- django_cfg/core/builders/security_builder.py +96 -0
- django_cfg/core/config.py +20 -892
- django_cfg/core/constants.py +69 -0
- django_cfg/core/environment/__init__.py +9 -0
- django_cfg/core/exceptions.py +45 -298
- django_cfg/core/generation/__init__.py +51 -0
- django_cfg/core/generation/core_generators/__init__.py +0 -0
- django_cfg/core/generation/core_generators/settings.py +90 -0
- django_cfg/core/generation/core_generators/static.py +82 -0
- django_cfg/core/generation/core_generators/templates.py +141 -0
- django_cfg/core/generation/data_generators/__init__.py +15 -0
- django_cfg/core/generation/data_generators/cache.py +132 -0
- django_cfg/core/generation/data_generators/database.py +117 -0
- django_cfg/core/generation/generation.py +92 -0
- django_cfg/core/generation/integration_generators/__init__.py +21 -0
- django_cfg/core/generation/integration_generators/api.py +237 -0
- django_cfg/core/generation/integration_generators/sessions.py +65 -0
- django_cfg/core/generation/integration_generators/tailwind.py +54 -0
- django_cfg/core/generation/integration_generators/tasks.py +92 -0
- django_cfg/core/generation/integration_generators/third_party.py +144 -0
- django_cfg/core/generation/orchestrator.py +285 -0
- django_cfg/core/generation/protocols.py +30 -0
- django_cfg/core/generation/security_generators/__init__.py +0 -0
- django_cfg/core/generation/utility_generators/__init__.py +24 -0
- django_cfg/core/generation/utility_generators/email.py +58 -0
- django_cfg/core/generation/utility_generators/i18n.py +66 -0
- django_cfg/core/generation/utility_generators/limits.py +58 -0
- django_cfg/core/generation/utility_generators/logging.py +66 -0
- django_cfg/core/generation/utility_generators/security.py +101 -0
- django_cfg/core/generation/utils/__init__.py +0 -0
- django_cfg/core/generation/utils/helpers.py +32 -0
- django_cfg/core/integration/__init__.py +18 -25
- django_cfg/core/integration/display/startup.py +146 -133
- django_cfg/core/integration/url_integration.py +13 -2
- django_cfg/core/services/__init__.py +5 -0
- django_cfg/core/services/config_service.py +121 -0
- django_cfg/core/state/__init__.py +9 -0
- django_cfg/core/state/registry.py +84 -0
- django_cfg/core/types/__init__.py +15 -0
- django_cfg/core/types/aliases.py +15 -0
- django_cfg/core/types/enums.py +49 -0
- django_cfg/dashboard/DEBUG_README.md +105 -0
- django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
- django_cfg/dashboard/__init__.py +24 -0
- django_cfg/dashboard/components.py +308 -0
- django_cfg/dashboard/debug.py +176 -0
- django_cfg/dashboard/management/__init__.py +0 -0
- django_cfg/dashboard/management/commands/__init__.py +0 -0
- django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
- django_cfg/dashboard/sections/__init__.py +1 -0
- django_cfg/dashboard/sections/base.py +128 -0
- django_cfg/dashboard/sections/commands.py +32 -0
- django_cfg/dashboard/sections/overview.py +394 -0
- django_cfg/dashboard/sections/stats.py +48 -0
- django_cfg/dashboard/sections/system.py +73 -0
- django_cfg/management/commands/check_settings.py +6 -2
- django_cfg/management/commands/clear_constance.py +6 -1
- django_cfg/management/commands/create_token.py +5 -4
- django_cfg/management/commands/generate.py +5 -0
- django_cfg/management/commands/list_urls.py +7 -2
- django_cfg/management/commands/migrate_all.py +6 -2
- django_cfg/management/commands/migrator.py +6 -1
- django_cfg/management/commands/rundramatiq.py +6 -1
- django_cfg/management/commands/rundramatiq_simulator.py +11 -4
- django_cfg/management/commands/runserver_ngrok.py +9 -7
- django_cfg/management/commands/script.py +25 -21
- django_cfg/management/commands/show_config.py +6 -1
- django_cfg/management/commands/show_urls.py +8 -3
- django_cfg/management/commands/superuser.py +5 -4
- django_cfg/management/commands/task_clear.py +8 -3
- django_cfg/management/commands/task_status.py +8 -3
- django_cfg/management/commands/test_email.py +6 -1
- django_cfg/management/commands/test_telegram.py +6 -1
- django_cfg/management/commands/test_twilio.py +6 -1
- django_cfg/management/commands/tree.py +7 -4
- django_cfg/models/__init__.py +88 -3
- django_cfg/models/api/__init__.py +27 -0
- django_cfg/models/{api.py → api/config.py} +1 -1
- django_cfg/models/api/drf/__init__.py +21 -0
- django_cfg/models/api/drf/config.py +101 -0
- django_cfg/models/api/drf/redoc.py +31 -0
- django_cfg/models/api/drf/spectacular.py +129 -0
- django_cfg/models/api/drf/swagger.py +59 -0
- django_cfg/models/{api_keys.py → api/keys.py} +16 -6
- django_cfg/models/{limits.py → api/limits.py} +0 -1
- django_cfg/models/base/__init__.py +14 -0
- django_cfg/models/django/__init__.py +16 -0
- django_cfg/models/{constance.py → django/constance.py} +1 -1
- django_cfg/models/{environment.py → django/environment.py} +1 -1
- django_cfg/models/infrastructure/__init__.py +17 -0
- django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
- django_cfg/models/infrastructure/database/__init__.py +22 -0
- django_cfg/models/infrastructure/database/config.py +265 -0
- django_cfg/models/infrastructure/database/converters.py +91 -0
- django_cfg/models/infrastructure/database/parsers.py +96 -0
- django_cfg/models/infrastructure/database/routing.py +85 -0
- django_cfg/models/infrastructure/database/validators.py +170 -0
- django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
- django_cfg/models/{security.py → infrastructure/security.py} +2 -2
- django_cfg/models/ngrok/__init__.py +11 -0
- django_cfg/models/ngrok/auth.py +37 -0
- django_cfg/models/ngrok/config.py +77 -0
- django_cfg/models/ngrok/tunnel.py +35 -0
- django_cfg/models/payments/__init__.py +20 -0
- django_cfg/models/payments/api_keys.py +57 -0
- django_cfg/models/{payments.py → payments/config.py} +56 -154
- django_cfg/models/payments/providers/__init__.py +15 -0
- django_cfg/models/payments/providers/base.py +25 -0
- django_cfg/models/payments/providers/nowpayments.py +48 -0
- django_cfg/models/services/__init__.py +18 -0
- django_cfg/models/services/base.py +65 -0
- django_cfg/models/{email.py → services/email.py} +1 -1
- django_cfg/models/services/telegram.py +172 -0
- django_cfg/models/tasks/__init__.py +51 -0
- django_cfg/models/tasks/backends.py +250 -0
- django_cfg/models/tasks/config.py +314 -0
- django_cfg/models/tasks/utils.py +174 -0
- django_cfg/modules/base.py +18 -3
- django_cfg/modules/django_admin/decorators/actions.py +1 -1
- django_cfg/modules/django_admin/decorators/display.py +1 -1
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
- django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
- django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
- django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
- django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
- django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
- django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
- django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
- django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
- django_cfg/modules/django_drf_theme/README.md +207 -0
- django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
- django_cfg/modules/django_drf_theme/__init__.py +23 -0
- django_cfg/modules/django_drf_theme/apps.py +15 -0
- django_cfg/modules/django_drf_theme/renderers.py +58 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
- django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
- django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
- django_cfg/modules/django_email/__init__.py +14 -0
- django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
- django_cfg/modules/django_email/utils.py +40 -0
- django_cfg/modules/django_health/__init__.py +9 -0
- django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
- django_cfg/modules/django_llm/llm/client.py +155 -550
- django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
- django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
- django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
- django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
- django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
- django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
- django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
- django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
- django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
- django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
- django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
- django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
- django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
- django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
- django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
- django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
- django_cfg/modules/django_llm/translator/translator.py +150 -603
- django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
- django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
- django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
- django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
- django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
- django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
- django_cfg/modules/django_logging/__init__.py +14 -0
- django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
- django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
- django_cfg/modules/django_ngrok/__init__.py +39 -0
- django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
- django_cfg/modules/django_rpc_old/POETRY.md +344 -0
- django_cfg/modules/django_rpc_old/README.md +397 -0
- django_cfg/modules/django_rpc_old/TESTING.md +358 -0
- django_cfg/modules/django_rpc_old/__init__.py +39 -0
- django_cfg/modules/django_rpc_old/client.py +531 -0
- django_cfg/modules/django_rpc_old/config.py +279 -0
- django_cfg/modules/django_rpc_old/exceptions.py +172 -0
- django_cfg/modules/django_tailwind/README.md +478 -0
- django_cfg/modules/django_tailwind/__init__.py +7 -0
- django_cfg/modules/django_tailwind/apps.py +10 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
- django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
- django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
- django_cfg/modules/django_tasks/__init__.py +29 -0
- django_cfg/modules/django_tasks/factory.py +127 -0
- django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
- django_cfg/modules/django_tasks/settings.py +107 -0
- django_cfg/modules/django_telegram/__init__.py +29 -0
- django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
- django_cfg/modules/django_telegram/utils.py +62 -0
- django_cfg/modules/django_twilio/__init__.py +54 -107
- django_cfg/modules/django_twilio/_imports.py +30 -0
- django_cfg/modules/django_twilio/base.py +192 -0
- django_cfg/modules/django_twilio/email_otp.py +227 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
- django_cfg/modules/django_twilio/simple_service.py +1 -2
- django_cfg/modules/django_twilio/sms.py +94 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -3
- django_cfg/modules/django_twilio/unified.py +310 -0
- django_cfg/modules/django_twilio/utils.py +190 -0
- django_cfg/modules/django_twilio/whatsapp.py +137 -0
- django_cfg/modules/django_unfold/callbacks/base.py +198 -7
- django_cfg/modules/django_unfold/callbacks/main.py +102 -10
- django_cfg/modules/django_unfold/dashboard.py +65 -43
- django_cfg/modules/django_unfold/models/config.py +13 -12
- django_cfg/modules/django_unfold/models/navigation.py +8 -3
- django_cfg/modules/django_unfold/models/tabs.py +2 -2
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
- django_cfg/registry/core.py +24 -26
- django_cfg/registry/modules.py +5 -2
- django_cfg/registry/services.py +20 -3
- django_cfg/registry/third_party.py +8 -8
- django_cfg/static/admin/css/dashboard.css +260 -0
- django_cfg/static/admin/js/commands.js +171 -0
- django_cfg/static/admin/js/dashboard.js +126 -0
- django_cfg/templates/admin/components/management_commands.js +375 -0
- django_cfg/templates/admin/components/progress_bar.html +18 -23
- django_cfg/templates/admin/index.html +48 -20
- django_cfg/templates/admin/index_new.html +106 -0
- django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
- django_cfg/templates/admin/sections/commands_section.html +626 -0
- django_cfg/templates/admin/sections/overview_section.html +112 -0
- django_cfg/templates/admin/sections/stats_section.html +35 -0
- django_cfg/templates/admin/sections/system_section.html +99 -0
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
- django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
- django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
- django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
- django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
- django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
- django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
- django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
- django_cfg/templatetags/django_cfg.py +7 -1
- django_cfg/utils/smart_defaults.py +4 -4
- django_cfg-1.4.0.dist-info/METADATA +920 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/RECORD +425 -196
- django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
- django_cfg/core/generation.py +0 -621
- django_cfg/management/commands/validate_config.py +0 -189
- django_cfg/models/database.py +0 -480
- django_cfg/models/drf.py +0 -272
- django_cfg/models/ngrok.py +0 -122
- django_cfg/models/services.py +0 -440
- django_cfg/models/tasks.py +0 -550
- django_cfg/modules/django_twilio/service.py +0 -942
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/rest_framework/api.html +0 -12
- django_cfg/utils/toolkit.py +0 -703
- django_cfg-1.3.11.dist-info/METADATA +0 -1029
- /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
- /django_cfg/core/{environment.py → environment/detector.py} +0 -0
- /django_cfg/models/{cors.py → api/cors.py} +0 -0
- /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
- /django_cfg/models/{base.py → base/config.py} +0 -0
- /django_cfg/models/{cfg.py → base/module.py} +0 -0
- /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
- /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,83 +1,54 @@
|
|
1
1
|
"""
|
2
|
-
Django Translator Service for django_llm.
|
2
|
+
Django Translator Service orchestrator for django_llm.
|
3
3
|
|
4
4
|
Auto-configuring translation service with language detection and JSON support.
|
5
5
|
"""
|
6
6
|
|
7
|
-
import json
|
8
7
|
import logging
|
9
|
-
import
|
10
|
-
import re
|
11
|
-
from typing import Dict, List, Optional, Any, Set
|
12
|
-
from datetime import datetime
|
13
|
-
from pathlib import Path
|
8
|
+
from typing import Dict, Any, Optional
|
14
9
|
|
15
10
|
from django_cfg.modules import BaseCfgModule
|
16
11
|
from ..llm.client import LLMClient
|
17
|
-
from ..llm.cache import LLMCache
|
18
12
|
from .cache import TranslationCacheManager
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
pass
|
14
|
+
# Import specialized components
|
15
|
+
from .detectors import ScriptDetector, LanguageDetector
|
16
|
+
from .translators import TextTranslator, JsonTranslator, TranslationError, LanguageDetectionError
|
17
|
+
from .utils import TextUtils, PromptBuilder
|
18
|
+
from .stats import StatsTracker
|
26
19
|
|
27
|
-
|
28
|
-
class LanguageDetectionError(TranslationError):
|
29
|
-
"""Raised when language detection fails."""
|
30
|
-
pass
|
20
|
+
logger = logging.getLogger(__name__)
|
31
21
|
|
32
22
|
|
33
23
|
class DjangoTranslator(BaseCfgModule):
|
34
24
|
"""
|
35
|
-
Translation
|
36
|
-
|
37
|
-
|
38
|
-
|
25
|
+
Translation service orchestrator for django_cfg.
|
26
|
+
|
27
|
+
Coordinates translation using specialized components:
|
28
|
+
- ScriptDetector: Script-based language detection
|
29
|
+
- LanguageDetector: Dictionary-based language detection
|
30
|
+
- TextTranslator: Single text translation
|
31
|
+
- JsonTranslator: JSON object translation
|
32
|
+
- StatsTracker: Usage statistics
|
39
33
|
"""
|
40
34
|
|
41
35
|
def __init__(self, client=None):
|
42
36
|
self._client = client
|
43
37
|
self._is_configured = None
|
44
|
-
|
45
|
-
|
46
|
-
# Language mappings
|
47
|
-
self.language_names = {
|
48
|
-
"en": "English", "ru": "Russian", "ko": "Korean", "zh": "Chinese",
|
49
|
-
"ja": "Japanese", "es": "Spanish", "fr": "French", "de": "German",
|
50
|
-
"it": "Italian", "pt": "Portuguese", "ar": "Arabic", "hi": "Hindi",
|
51
|
-
"tr": "Turkish", "pl": "Polish", "uk": "Ukrainian", "be": "Belarusian",
|
52
|
-
"kk": "Kazakh"
|
53
|
-
}
|
54
|
-
|
55
|
-
# CJK character ranges for detection
|
56
|
-
self.cjk_ranges = [
|
57
|
-
(0x4E00, 0x9FFF), # CJK Unified Ideographs
|
58
|
-
(0x3400, 0x4DBF), # CJK Extension A
|
59
|
-
(0x20000, 0x2A6DF), # CJK Extension B
|
60
|
-
(0x2A700, 0x2B73F), # CJK Extension C
|
61
|
-
(0x2B740, 0x2B81F), # CJK Extension D
|
62
|
-
(0x3040, 0x309F), # Hiragana
|
63
|
-
(0x30A0, 0x30FF), # Katakana
|
64
|
-
(0xAC00, 0xD7AF), # Hangul Syllables
|
65
|
-
]
|
66
|
-
|
67
|
-
# Initialize translation cache manager (like in unreal_llm)
|
38
|
+
|
39
|
+
# Initialize translation cache manager
|
68
40
|
self.translation_cache = TranslationCacheManager()
|
69
|
-
|
70
|
-
#
|
71
|
-
self.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
}
|
41
|
+
|
42
|
+
# Initialize components
|
43
|
+
self.script_detector = ScriptDetector()
|
44
|
+
self.language_detector = LanguageDetector()
|
45
|
+
self.text_utils = TextUtils()
|
46
|
+
self.prompt_builder = PromptBuilder()
|
47
|
+
self.stats_tracker = StatsTracker()
|
48
|
+
|
49
|
+
# Initialize translators (will be created when client is available)
|
50
|
+
self._text_translator = None
|
51
|
+
self._json_translator = None
|
81
52
|
|
82
53
|
@property
|
83
54
|
def config(self):
|
@@ -96,8 +67,8 @@ class DjangoTranslator(BaseCfgModule):
|
|
96
67
|
elif hasattr(self.config, 'llm') and self.config.llm:
|
97
68
|
llm_config = self.config.llm
|
98
69
|
self._is_configured = (
|
99
|
-
hasattr(llm_config, 'api_key') and
|
100
|
-
llm_config.api_key and
|
70
|
+
hasattr(llm_config, 'api_key') and
|
71
|
+
llm_config.api_key and
|
101
72
|
len(llm_config.api_key.strip()) > 0
|
102
73
|
)
|
103
74
|
else:
|
@@ -114,165 +85,69 @@ class DjangoTranslator(BaseCfgModule):
|
|
114
85
|
raise ValueError("LLM client not configured. Pass client to constructor.")
|
115
86
|
return self._client
|
116
87
|
|
88
|
+
@property
|
89
|
+
def text_translator(self) -> TextTranslator:
|
90
|
+
"""Get or create text translator instance."""
|
91
|
+
if self._text_translator is None:
|
92
|
+
self._text_translator = TextTranslator(
|
93
|
+
self.client,
|
94
|
+
self.translation_cache,
|
95
|
+
self.stats_tracker,
|
96
|
+
self.script_detector,
|
97
|
+
self.text_utils,
|
98
|
+
self.prompt_builder
|
99
|
+
)
|
100
|
+
return self._text_translator
|
101
|
+
|
102
|
+
@property
|
103
|
+
def json_translator(self) -> JsonTranslator:
|
104
|
+
"""Get or create JSON translator instance."""
|
105
|
+
if self._json_translator is None:
|
106
|
+
self._json_translator = JsonTranslator(
|
107
|
+
self.client,
|
108
|
+
self.translation_cache,
|
109
|
+
self.language_detector,
|
110
|
+
self.text_utils,
|
111
|
+
self.prompt_builder
|
112
|
+
)
|
113
|
+
return self._json_translator
|
114
|
+
|
117
115
|
def detect_language(self, text: str) -> str:
|
118
116
|
"""
|
119
|
-
Detect language of text
|
120
|
-
|
117
|
+
Detect language of text.
|
118
|
+
|
119
|
+
Delegates to ScriptDetector.
|
120
|
+
|
121
121
|
Args:
|
122
122
|
text: Text to analyze
|
123
|
-
|
123
|
+
|
124
124
|
Returns:
|
125
125
|
Language code
|
126
126
|
"""
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# Check for CJK characters
|
137
|
-
if self._contains_cjk(text):
|
138
|
-
# Simple CJK detection
|
139
|
-
if self._contains_korean(text):
|
140
|
-
return 'ko'
|
141
|
-
elif self._contains_japanese(text):
|
142
|
-
return 'ja'
|
143
|
-
else:
|
144
|
-
return 'zh' # Default to Chinese for other CJK
|
145
|
-
|
146
|
-
# Check for Cyrillic (Russian/Ukrainian/Belarusian)
|
147
|
-
if self._contains_cyrillic(text):
|
148
|
-
return 'ru' # Default to Russian for Cyrillic
|
149
|
-
|
150
|
-
# Default to English for Latin script
|
151
|
-
return 'en'
|
152
|
-
|
153
|
-
def _contains_cjk(self, text: str) -> bool:
|
154
|
-
"""Check if text contains CJK characters."""
|
155
|
-
for char in text:
|
156
|
-
char_code = ord(char)
|
157
|
-
for start, end in self.cjk_ranges:
|
158
|
-
if start <= char_code <= end:
|
159
|
-
return True
|
160
|
-
return False
|
161
|
-
|
162
|
-
def _contains_korean(self, text: str) -> bool:
|
163
|
-
"""Check if text contains Korean characters."""
|
164
|
-
for char in text:
|
165
|
-
char_code = ord(char)
|
166
|
-
if 0xAC00 <= char_code <= 0xD7AF: # Hangul Syllables
|
167
|
-
return True
|
168
|
-
return False
|
169
|
-
|
170
|
-
def _contains_japanese(self, text: str) -> bool:
|
171
|
-
"""Check if text contains Japanese characters."""
|
172
|
-
for char in text:
|
173
|
-
char_code = ord(char)
|
174
|
-
if (0x3040 <= char_code <= 0x309F or # Hiragana
|
175
|
-
0x30A0 <= char_code <= 0x30FF): # Katakana
|
176
|
-
return True
|
177
|
-
return False
|
178
|
-
|
179
|
-
def _contains_cyrillic(self, text: str) -> bool:
|
180
|
-
"""Check if text contains Cyrillic characters."""
|
181
|
-
for char in text:
|
182
|
-
char_code = ord(char)
|
183
|
-
if 0x0400 <= char_code <= 0x04FF: # Cyrillic
|
184
|
-
return True
|
185
|
-
return False
|
186
|
-
|
187
|
-
def _clean_text(self, text: str) -> str:
|
188
|
-
"""Clean text for better language detection."""
|
189
|
-
if not text:
|
190
|
-
return ""
|
191
|
-
|
192
|
-
# Remove excessive whitespace
|
193
|
-
text = ' '.join(text.split())
|
194
|
-
|
195
|
-
# Remove URLs and technical terms
|
196
|
-
text = re.sub(r'https?://\S+', '', text)
|
197
|
-
text = re.sub(r'www\.\S+', '', text)
|
198
|
-
text = re.sub(r'\b\d+\b', '', text) # Remove standalone numbers
|
199
|
-
|
200
|
-
return text.strip()
|
201
|
-
|
202
|
-
def needs_translation(self, text: str, source_language: str, target_language: str) -> bool:
|
127
|
+
return self.script_detector.detect_language(text)
|
128
|
+
|
129
|
+
def needs_translation(
|
130
|
+
self,
|
131
|
+
text: str,
|
132
|
+
source_language: str,
|
133
|
+
target_language: str
|
134
|
+
) -> bool:
|
203
135
|
"""
|
204
136
|
Determine if text needs translation.
|
205
|
-
|
137
|
+
|
138
|
+
Delegates to TextUtils.
|
139
|
+
|
206
140
|
Args:
|
207
141
|
text: Text to check
|
208
142
|
source_language: Source language code
|
209
143
|
target_language: Target language code
|
210
|
-
|
144
|
+
|
211
145
|
Returns:
|
212
146
|
True if translation is needed
|
213
147
|
"""
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
# Skip URLs and technical content
|
218
|
-
if self._is_technical_content(text):
|
219
|
-
return False
|
220
|
-
|
221
|
-
# If source and target are the same, no translation needed
|
222
|
-
if source_language == target_language:
|
223
|
-
return False
|
224
|
-
|
225
|
-
# Force translation for CJK content
|
226
|
-
if self._contains_cjk(text):
|
227
|
-
return True
|
228
|
-
|
229
|
-
# Auto-detect if source is 'auto'
|
230
|
-
if source_language == 'auto':
|
231
|
-
detected_lang = self.detect_language(text)
|
232
|
-
return detected_lang != target_language
|
233
|
-
|
234
|
-
return True
|
235
|
-
|
236
|
-
def _is_technical_content(self, text: str) -> bool:
|
237
|
-
"""Check if text is technical content that shouldn't be translated."""
|
238
|
-
# URLs
|
239
|
-
if text.startswith(('http://', 'https://', '//', 'www.')):
|
240
|
-
return True
|
241
|
-
|
242
|
-
# File paths
|
243
|
-
if '/' in text and ('.' in text or text.startswith('/')):
|
244
|
-
return True
|
245
|
-
|
246
|
-
# Numbers only
|
247
|
-
if re.match(r'^\d+(\.\d+)?$', text.strip()):
|
248
|
-
return True
|
249
|
-
|
250
|
-
# Technical identifiers
|
251
|
-
if re.match(r'^[A-Z_][A-Z0-9_]*$', text):
|
252
|
-
return True
|
253
|
-
|
254
|
-
return False
|
255
|
-
|
256
|
-
def _get_translation_prompt(self, text: str, source_language: str, target_language: str) -> str:
|
257
|
-
"""Generate translation prompt."""
|
258
|
-
source_name = self.language_names.get(source_language, source_language)
|
259
|
-
target_name = self.language_names.get(target_language, target_language)
|
260
|
-
|
261
|
-
prompt = f"""You are a professional translator. Translate the following text from {source_name} to {target_name}.
|
262
|
-
|
263
|
-
IMPORTANT INSTRUCTIONS:
|
264
|
-
1. Translate ONLY the text provided
|
265
|
-
2. Preserve original formatting, numbers, URLs, and technical values
|
266
|
-
3. Keep the translation accurate and natural
|
267
|
-
4. Return ONLY the translation, no explanations or comments
|
268
|
-
5. If the text contains mixed languages, translate only the parts in {source_name}
|
269
|
-
|
270
|
-
Text to translate:
|
271
|
-
{text}
|
272
|
-
|
273
|
-
Translation:"""
|
274
|
-
|
275
|
-
return prompt
|
148
|
+
return self.text_utils.needs_translation(
|
149
|
+
text, source_language, target_language, self.script_detector
|
150
|
+
)
|
276
151
|
|
277
152
|
def translate(
|
278
153
|
self,
|
@@ -286,11 +161,15 @@ Translation:"""
|
|
286
161
|
"""
|
287
162
|
Translate single text.
|
288
163
|
|
164
|
+
Delegates to TextTranslator.
|
165
|
+
|
289
166
|
Args:
|
290
167
|
text: Text to translate
|
291
168
|
target_language: Target language code
|
292
169
|
source_language: Source language code ('auto' for detection)
|
293
170
|
fail_silently: Don't raise exceptions on failure
|
171
|
+
model: Optional model override
|
172
|
+
temperature: Optional temperature override
|
294
173
|
|
295
174
|
Returns:
|
296
175
|
Translated text
|
@@ -306,69 +185,19 @@ Translation:"""
|
|
306
185
|
raise TranslationError(error_msg)
|
307
186
|
return text
|
308
187
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
if not fail_silently:
|
315
|
-
raise LanguageDetectionError("Could not detect source language")
|
316
|
-
return text
|
317
|
-
|
318
|
-
# Check if translation is needed
|
319
|
-
if not self.needs_translation(text, source_language, target_language):
|
320
|
-
return text
|
321
|
-
|
322
|
-
# Check translation cache (by language pair like in unreal_llm)
|
323
|
-
cached_translation = self.translation_cache.get(text, source_language, target_language)
|
324
|
-
if cached_translation:
|
325
|
-
self.stats['cache_hits'] += 1
|
326
|
-
return cached_translation
|
327
|
-
|
328
|
-
self.stats['cache_misses'] += 1
|
329
|
-
|
330
|
-
# Generate prompt
|
331
|
-
prompt = self._get_translation_prompt(text, source_language, target_language)
|
332
|
-
|
333
|
-
# Use LLM client for translation
|
334
|
-
messages = [{"role": "user", "content": prompt}]
|
335
|
-
response = self.client.chat_completion(
|
336
|
-
messages=messages,
|
188
|
+
return self.text_translator.translate(
|
189
|
+
text=text,
|
190
|
+
target_language=target_language,
|
191
|
+
source_language=source_language,
|
192
|
+
fail_silently=fail_silently,
|
337
193
|
model=model,
|
338
|
-
temperature=temperature
|
339
|
-
max_tokens=1000
|
194
|
+
temperature=temperature
|
340
195
|
)
|
341
196
|
|
342
|
-
# Extract translation
|
343
|
-
translated_text = response.get('content', '').strip()
|
344
|
-
|
345
|
-
if not translated_text:
|
346
|
-
if not fail_silently:
|
347
|
-
raise TranslationError("Empty translation response")
|
348
|
-
return text
|
349
|
-
|
350
|
-
# Cache the result (by language pair like in unreal_llm)
|
351
|
-
self.translation_cache.set(text, source_language, target_language, translated_text)
|
352
|
-
|
353
|
-
# Update stats
|
354
|
-
self.stats['total_translations'] += 1
|
355
|
-
self.stats['successful_translations'] += 1
|
356
|
-
if response.get('tokens_used'):
|
357
|
-
self.stats['total_tokens_used'] += response['tokens_used']
|
358
|
-
if response.get('cost_usd'):
|
359
|
-
self.stats['total_cost_usd'] += response['cost_usd']
|
360
|
-
|
361
|
-
lang_pair = f"{source_language}-{target_language}"
|
362
|
-
self.stats['language_pairs'][lang_pair] = self.stats['language_pairs'].get(lang_pair, 0) + 1
|
363
|
-
|
364
|
-
return translated_text
|
365
|
-
|
366
197
|
except Exception as e:
|
367
|
-
|
368
|
-
error_msg = f"Failed to translate text: {e}"
|
369
|
-
logger.error(error_msg)
|
198
|
+
logger.error(f"Translation failed: {e}")
|
370
199
|
if not fail_silently:
|
371
|
-
raise
|
200
|
+
raise
|
372
201
|
return text
|
373
202
|
|
374
203
|
def translate_json(
|
@@ -381,382 +210,90 @@ Translation:"""
|
|
381
210
|
temperature: Optional[float] = None,
|
382
211
|
) -> Dict[str, Any]:
|
383
212
|
"""
|
384
|
-
Translate JSON object
|
213
|
+
Translate JSON object.
|
214
|
+
|
215
|
+
Delegates to JsonTranslator.
|
385
216
|
|
386
217
|
Args:
|
387
218
|
data: JSON object to translate
|
388
219
|
target_language: Target language for translation
|
389
220
|
source_language: Source language ('auto' for detection)
|
390
221
|
fail_silently: Don't raise exceptions on failure
|
222
|
+
model: Optional model override
|
223
|
+
temperature: Optional temperature override
|
391
224
|
|
392
225
|
Returns:
|
393
226
|
Translated JSON object
|
227
|
+
|
228
|
+
Raises:
|
229
|
+
TranslationError: If translation fails and fail_silently is False
|
394
230
|
"""
|
395
231
|
try:
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
232
|
+
if not self.is_configured:
|
233
|
+
error_msg = "Translation service is not configured"
|
234
|
+
logger.error(error_msg)
|
235
|
+
if not fail_silently:
|
236
|
+
raise TranslationError(error_msg)
|
401
237
|
return data
|
402
238
|
|
403
|
-
|
404
|
-
|
405
|
-
# Translate entire JSON in one request
|
406
|
-
return self._translate_json_batch(
|
239
|
+
return self.json_translator.translate_json(
|
407
240
|
data=data,
|
408
241
|
target_language=target_language,
|
409
242
|
source_language=source_language,
|
243
|
+
fail_silently=fail_silently,
|
410
244
|
model=model,
|
411
|
-
temperature=temperature
|
412
|
-
fail_silently=fail_silently
|
245
|
+
temperature=temperature
|
413
246
|
)
|
414
247
|
|
415
248
|
except Exception as e:
|
416
|
-
|
417
|
-
logger.error(error_msg)
|
249
|
+
logger.error(f"JSON translation failed: {e}")
|
418
250
|
if not fail_silently:
|
419
|
-
raise
|
251
|
+
raise
|
420
252
|
return data
|
421
253
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
425
|
-
"""Translate JSON object with smart text-level caching."""
|
426
|
-
try:
|
427
|
-
# Extract all translatable texts from JSON
|
428
|
-
translatable_texts = self._extract_translatable_texts(data, source_language, target_language)
|
429
|
-
|
430
|
-
if not translatable_texts:
|
431
|
-
logger.info("No texts need translation in JSON object")
|
432
|
-
return data
|
433
|
-
|
434
|
-
# Detect actual source language from first text if auto
|
435
|
-
actual_source_lang = source_language
|
436
|
-
if source_language == 'auto' and translatable_texts:
|
437
|
-
first_text = list(translatable_texts)[0]
|
438
|
-
detected_lang = self._detect_language(first_text)
|
439
|
-
if detected_lang and detected_lang != 'unknown':
|
440
|
-
actual_source_lang = detected_lang
|
441
|
-
logger.info(f"Detected source language: {actual_source_lang}")
|
442
|
-
else:
|
443
|
-
actual_source_lang = 'en' # fallback to English
|
444
|
-
logger.info(f"Language detection failed, using fallback: {actual_source_lang}")
|
445
|
-
|
446
|
-
# Check cache for each text and separate cached vs uncached
|
447
|
-
cached_translations = {}
|
448
|
-
uncached_texts = []
|
449
|
-
|
450
|
-
for text in translatable_texts:
|
451
|
-
cached_translation = self.translation_cache.get(text, actual_source_lang, target_language)
|
452
|
-
if cached_translation:
|
453
|
-
cached_translations[text] = cached_translation
|
454
|
-
logger.debug(f"Cache hit for text: '{text[:50]}...'")
|
455
|
-
else:
|
456
|
-
uncached_texts.append(text)
|
457
|
-
|
458
|
-
logger.info(f"Found {len(cached_translations)} cached translations, {len(uncached_texts)} need translation")
|
459
|
-
|
460
|
-
# If everything is cached, just reconstruct
|
461
|
-
if not uncached_texts:
|
462
|
-
logger.info("All translations found in cache, reconstructing JSON")
|
463
|
-
return self._apply_translations(data, cached_translations)
|
464
|
-
|
465
|
-
# Create JSON with only uncached texts for LLM
|
466
|
-
uncached_json = self._create_partial_json(data, uncached_texts)
|
467
|
-
json_str = json.dumps(uncached_json, ensure_ascii=False, indent=2)
|
468
|
-
|
469
|
-
# Create translation prompt
|
470
|
-
prompt = f"""You are a professional translator. Your task is to translate ONLY the VALUES in this JSON, NEVER the keys.
|
471
|
-
|
472
|
-
🚨 CRITICAL RULES - VIOLATION WILL RESULT IN FAILURE:
|
473
|
-
1. ❌ NEVER TRANSLATE JSON KEYS: "title" stays "title", NOT "título" or "заголовок"
|
474
|
-
2. ❌ NEVER TRANSLATE JSON KEYS: "description" stays "description", NOT "descripción" or "описание"
|
475
|
-
3. ❌ NEVER TRANSLATE JSON KEYS: "navigation" stays "navigation", NOT "navegación" or "навигация"
|
476
|
-
4. ✅ ONLY translate the VALUES: "Hello" → "Hola", "World" → "Mundo"
|
477
|
-
5. ❌ DO NOT translate: URLs, emails, numbers, booleans, null, empty strings, "SKIP_TRANSLATION"
|
478
|
-
6. ✅ Keep exact JSON structure and key names in English
|
479
|
-
|
480
|
-
WRONG EXAMPLE (DO NOT DO THIS):
|
481
|
-
{{"título": "Hola", "descripción": "Mundo"}}
|
482
|
-
|
483
|
-
CORRECT EXAMPLE (DO THIS):
|
484
|
-
{{"title": "Hola", "description": "Mundo"}}
|
485
|
-
|
486
|
-
If you translate ANY JSON key, you have FAILED the task completely.
|
487
|
-
|
488
|
-
JSON to translate from {actual_source_lang} to {target_language}:
|
489
|
-
{json_str}
|
490
|
-
|
491
|
-
Return ONLY the JSON with translated VALUES and original English keys:"""
|
492
|
-
|
493
|
-
# Make LLM request for uncached texts only
|
494
|
-
response = self.client.chat_completion(
|
495
|
-
messages=[{"role": "user", "content": prompt}],
|
496
|
-
model=model,
|
497
|
-
temperature=temperature if temperature is not None else 0.1,
|
498
|
-
max_tokens=4000
|
499
|
-
)
|
500
|
-
|
501
|
-
translated_json_str = response.get("content", "").strip()
|
502
|
-
|
503
|
-
# Parse LLM response
|
504
|
-
try:
|
505
|
-
# Remove any markdown formatting if present
|
506
|
-
if translated_json_str.startswith("```json"):
|
507
|
-
translated_json_str = translated_json_str.replace("```json", "").replace("```", "").strip()
|
508
|
-
elif translated_json_str.startswith("```"):
|
509
|
-
translated_json_str = translated_json_str.replace("```", "").strip()
|
510
|
-
|
511
|
-
translated_partial_data = json.loads(translated_json_str)
|
512
|
-
|
513
|
-
# Extract new translations by comparing original with translated
|
514
|
-
new_translations = self._extract_translations_by_comparison(
|
515
|
-
uncached_json, translated_partial_data, uncached_texts
|
516
|
-
)
|
517
|
-
|
518
|
-
# Cache new translations
|
519
|
-
for original_text, translated_text in new_translations.items():
|
520
|
-
self.translation_cache.set(original_text, actual_source_lang, target_language, translated_text)
|
521
|
-
logger.debug(f"Cached new translation: '{original_text[:30]}...' -> '{translated_text[:30]}...'")
|
522
|
-
|
523
|
-
# Combine cached + new translations
|
524
|
-
all_translations = {**cached_translations, **new_translations}
|
525
|
-
|
526
|
-
# Reconstruct full JSON with all translations
|
527
|
-
result = self._apply_translations(data, all_translations)
|
528
|
-
|
529
|
-
logger.info(f"Successfully translated JSON: {len(cached_translations)} from cache, {len(new_translations)} new")
|
530
|
-
return result
|
531
|
-
|
532
|
-
except json.JSONDecodeError as e:
|
533
|
-
logger.error(f"LLM returned invalid JSON: {e}")
|
534
|
-
logger.error(f"Response: {translated_json_str[:500]}...")
|
535
|
-
|
536
|
-
if fail_silently:
|
537
|
-
# Fallback: use only cached translations
|
538
|
-
return self._apply_translations(data, cached_translations)
|
539
|
-
else:
|
540
|
-
raise TranslationError(f"LLM returned invalid JSON: {e}")
|
541
|
-
|
542
|
-
except Exception as e:
|
543
|
-
logger.error(f"Batch JSON translation failed: {e}")
|
544
|
-
if fail_silently:
|
545
|
-
return data
|
546
|
-
else:
|
547
|
-
raise TranslationError(f"Batch JSON translation failed: {e}")
|
548
|
-
|
549
|
-
def _extract_translatable_texts(self, obj: Any, source_language: str, target_language: str) -> Set[str]:
|
550
|
-
"""Extract texts that need translation from JSON object."""
|
551
|
-
translatable_texts = set()
|
552
|
-
|
553
|
-
def _extract_recursive(item):
|
554
|
-
if isinstance(item, str):
|
555
|
-
if self.needs_translation(item, source_language, target_language):
|
556
|
-
translatable_texts.add(item)
|
557
|
-
elif isinstance(item, list):
|
558
|
-
for sub_item in item:
|
559
|
-
_extract_recursive(sub_item)
|
560
|
-
elif isinstance(item, dict):
|
561
|
-
for key, value in item.items():
|
562
|
-
# Check if key needs translation
|
563
|
-
if isinstance(key, str) and self.needs_translation(key, source_language, target_language):
|
564
|
-
translatable_texts.add(key)
|
565
|
-
# Check if value needs translation
|
566
|
-
_extract_recursive(value)
|
567
|
-
|
568
|
-
_extract_recursive(obj)
|
569
|
-
return translatable_texts
|
570
|
-
|
571
|
-
def _apply_translations(self, obj: Any, translations: Dict[str, str]) -> Any:
|
572
|
-
"""Apply translations to JSON object."""
|
573
|
-
if isinstance(obj, str):
|
574
|
-
return translations.get(obj, obj)
|
575
|
-
elif isinstance(obj, list):
|
576
|
-
return [self._apply_translations(item, translations) for item in obj]
|
577
|
-
elif isinstance(obj, dict):
|
578
|
-
translated_dict = {}
|
579
|
-
for key, value in obj.items():
|
580
|
-
# Translate key if it's in translations
|
581
|
-
translated_key = translations.get(key, key)
|
582
|
-
# Translate value
|
583
|
-
translated_value = self._apply_translations(value, translations)
|
584
|
-
translated_dict[translated_key] = translated_value
|
585
|
-
return translated_dict
|
586
|
-
else:
|
587
|
-
return obj
|
588
|
-
|
589
|
-
def _create_minimal_json(self, data: Any, uncached_texts: List[str]) -> Any:
|
590
|
-
"""Create a minimal JSON containing only uncached texts for LLM translation."""
|
591
|
-
uncached_set = set(uncached_texts)
|
592
|
-
|
593
|
-
def _filter_recursive(item):
|
594
|
-
if isinstance(item, str):
|
595
|
-
return item if item in uncached_set else None
|
596
|
-
elif isinstance(item, list):
|
597
|
-
filtered_list = []
|
598
|
-
for sub_item in item:
|
599
|
-
filtered = _filter_recursive(sub_item)
|
600
|
-
if filtered is not None:
|
601
|
-
filtered_list.append(filtered)
|
602
|
-
return filtered_list if filtered_list else None
|
603
|
-
elif isinstance(item, dict):
|
604
|
-
filtered_dict = {}
|
605
|
-
for key, value in item.items():
|
606
|
-
# Check if key needs translation
|
607
|
-
filtered_key = key if key in uncached_set else key
|
608
|
-
filtered_value = _filter_recursive(value)
|
609
|
-
|
610
|
-
# Include if key needs translation or value contains translatable content
|
611
|
-
if key in uncached_set or filtered_value is not None:
|
612
|
-
filtered_dict[filtered_key] = filtered_value if filtered_value is not None else value
|
613
|
-
|
614
|
-
return filtered_dict if filtered_dict else None
|
615
|
-
else:
|
616
|
-
return item
|
617
|
-
|
618
|
-
result = _filter_recursive(data)
|
619
|
-
return result if result is not None else {}
|
620
|
-
|
621
|
-
def _extract_translations_by_comparison(self, original_data: Any, translated_data: Any, uncached_texts: List[str]) -> Dict[str, str]:
|
622
|
-
"""Extract translations by comparing original and translated data."""
|
623
|
-
translations = {}
|
624
|
-
uncached_set = set(uncached_texts)
|
625
|
-
|
626
|
-
def _compare_recursive(original_item, translated_item):
|
627
|
-
if isinstance(original_item, str) and isinstance(translated_item, str):
|
628
|
-
if original_item in uncached_set and original_item != translated_item:
|
629
|
-
translations[original_item] = translated_item
|
630
|
-
logger.debug(f"Extracted translation: '{original_item}' -> '{translated_item}'")
|
631
|
-
elif isinstance(original_item, list) and isinstance(translated_item, list):
|
632
|
-
for orig, trans in zip(original_item, translated_item):
|
633
|
-
_compare_recursive(orig, trans)
|
634
|
-
elif isinstance(original_item, dict) and isinstance(translated_item, dict):
|
635
|
-
# Compare keys first
|
636
|
-
orig_keys = list(original_item.keys())
|
637
|
-
trans_keys = list(translated_item.keys())
|
638
|
-
|
639
|
-
for orig_key, trans_key in zip(orig_keys, trans_keys):
|
640
|
-
if orig_key in uncached_set and orig_key != trans_key:
|
641
|
-
translations[orig_key] = trans_key
|
642
|
-
logger.debug(f"Extracted key translation: '{orig_key}' -> '{trans_key}'")
|
643
|
-
|
644
|
-
# Compare values
|
645
|
-
for orig_key, orig_value in original_item.items():
|
646
|
-
# Find corresponding translated key
|
647
|
-
trans_key = translations.get(orig_key, orig_key)
|
648
|
-
if trans_key in translated_item:
|
649
|
-
_compare_recursive(orig_value, translated_item[trans_key])
|
650
|
-
|
651
|
-
_compare_recursive(original_data, translated_data)
|
652
|
-
|
653
|
-
logger.info(f"Extracted {len(translations)} translations from LLM response")
|
654
|
-
return translations
|
655
|
-
|
656
|
-
def _create_partial_json(self, data: Any, texts_to_include: List[str]) -> Any:
|
657
|
-
"""Create JSON containing only specified texts for translation."""
|
658
|
-
texts_set = set(texts_to_include)
|
659
|
-
|
660
|
-
def filter_recursive(obj):
|
661
|
-
if isinstance(obj, str):
|
662
|
-
# Include only texts that need translation
|
663
|
-
return obj if obj in texts_set else "SKIP_TRANSLATION"
|
664
|
-
elif isinstance(obj, dict):
|
665
|
-
result = {}
|
666
|
-
for key, value in obj.items():
|
667
|
-
filtered_value = filter_recursive(value)
|
668
|
-
# Only include if it contains translatable content
|
669
|
-
if self._contains_translatable_content(filtered_value, texts_set):
|
670
|
-
result[key] = filtered_value
|
671
|
-
return result
|
672
|
-
elif isinstance(obj, list):
|
673
|
-
result = []
|
674
|
-
for item in obj:
|
675
|
-
filtered_item = filter_recursive(item)
|
676
|
-
# Only include if it contains translatable content
|
677
|
-
if self._contains_translatable_content(filtered_item, texts_set):
|
678
|
-
result.append(filtered_item)
|
679
|
-
return result
|
680
|
-
else:
|
681
|
-
# Keep non-string values as is (numbers, booleans, null)
|
682
|
-
return obj
|
683
|
-
|
684
|
-
return filter_recursive(data)
|
685
|
-
|
686
|
-
def _contains_translatable_content(self, obj: Any, texts_set: set) -> bool:
|
687
|
-
"""Check if object contains any translatable text."""
|
688
|
-
if isinstance(obj, str):
|
689
|
-
return obj in texts_set
|
690
|
-
elif isinstance(obj, dict):
|
691
|
-
return any(self._contains_translatable_content(value, texts_set) for value in obj.values())
|
692
|
-
elif isinstance(obj, list):
|
693
|
-
return any(self._contains_translatable_content(item, texts_set) for item in obj)
|
694
|
-
else:
|
695
|
-
return False
|
254
|
+
def get_stats(self) -> Dict[str, Any]:
|
255
|
+
"""
|
256
|
+
Get translation statistics.
|
696
257
|
|
697
|
-
|
698
|
-
"""Detect language of text using simple heuristics."""
|
699
|
-
if not text or len(text.strip()) < 3:
|
700
|
-
return None
|
701
|
-
|
702
|
-
text_lower = text.lower().strip()
|
703
|
-
|
704
|
-
# Simple language detection based on common words
|
705
|
-
english_words = {'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'about', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'up', 'down', 'out', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 'can', 'will', 'just', 'should', 'now'}
|
706
|
-
russian_words = {'и', 'в', 'не', 'на', 'я', 'быть', 'он', 'с', 'что', 'а', 'по', 'это', 'она', 'этот', 'к', 'но', 'они', 'мы', 'как', 'из', 'у', 'который', 'то', 'за', 'свой', 'что', 'от', 'со', 'для', 'о', 'же', 'ты', 'все', 'если', 'люди', 'время', 'так', 'его', 'жизнь', 'может', 'год', 'только', 'над', 'еще', 'дом', 'после', 'большой', 'должен', 'хотеть', 'между'}
|
707
|
-
spanish_words = {'el', 'la', 'de', 'que', 'y', 'a', 'en', 'un', 'es', 'se', 'no', 'te', 'lo', 'le', 'da', 'su', 'por', 'son', 'con', 'para', 'al', 'del', 'los', 'las', 'una', 'como', 'pero', 'sus', 'le', 'ha', 'me', 'si', 'sin', 'sobre', 'este', 'ya', 'entre', 'cuando', 'todo', 'esta', 'ser', 'son', 'dos', 'también', 'fue', 'había', 'muy', 'hasta', 'desde', 'está'}
|
708
|
-
portuguese_words = {'o', 'a', 'de', 'que', 'e', 'do', 'da', 'em', 'um', 'para', 'é', 'com', 'não', 'uma', 'os', 'no', 'se', 'na', 'por', 'mais', 'as', 'dos', 'como', 'mas', 'foi', 'ao', 'ele', 'das', 'tem', 'à', 'seu', 'sua', 'ou', 'ser', 'quando', 'muito', 'há', 'nos', 'já', 'está', 'eu', 'também', 'só', 'pelo', 'pela', 'até', 'isso', 'ela', 'entre', 'era', 'depois', 'sem', 'mesmo', 'aos', 'ter', 'seus', 'suas', 'numa', 'pelos', 'pelas'}
|
709
|
-
|
710
|
-
words = set(text_lower.split())
|
711
|
-
|
712
|
-
# Count matches for each language
|
713
|
-
en_matches = len(words & english_words)
|
714
|
-
ru_matches = len(words & russian_words)
|
715
|
-
es_matches = len(words & spanish_words)
|
716
|
-
pt_matches = len(words & portuguese_words)
|
717
|
-
|
718
|
-
# Find the language with most matches
|
719
|
-
max_matches = max(en_matches, ru_matches, es_matches, pt_matches)
|
720
|
-
|
721
|
-
if max_matches == 0:
|
722
|
-
return 'en' # Default to English if no matches
|
723
|
-
|
724
|
-
if en_matches == max_matches:
|
725
|
-
return 'en'
|
726
|
-
elif ru_matches == max_matches:
|
727
|
-
return 'ru'
|
728
|
-
elif es_matches == max_matches:
|
729
|
-
return 'es'
|
730
|
-
elif pt_matches == max_matches:
|
731
|
-
return 'pt'
|
732
|
-
|
733
|
-
return 'en' # Default fallback
|
258
|
+
Delegates to StatsTracker.
|
734
259
|
|
735
|
-
|
736
|
-
|
737
|
-
|
260
|
+
Returns:
|
261
|
+
Dictionary with statistics
|
262
|
+
"""
|
263
|
+
return self.stats_tracker.get_stats()
|
738
264
|
|
739
265
|
def clear_cache(self) -> bool:
|
740
|
-
"""
|
266
|
+
"""
|
267
|
+
Clear translation cache.
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
True if successful
|
271
|
+
"""
|
741
272
|
try:
|
742
|
-
self.
|
743
|
-
self.
|
273
|
+
self.translation_cache.clear()
|
274
|
+
if self._client:
|
275
|
+
self.client.clear_cache()
|
744
276
|
return True
|
745
277
|
except Exception as e:
|
746
278
|
logger.error(f"Failed to clear translation cache: {e}")
|
747
279
|
return False
|
748
280
|
|
749
281
|
def get_config_info(self) -> Dict[str, Any]:
|
750
|
-
"""
|
282
|
+
"""
|
283
|
+
Get translation service configuration information.
|
284
|
+
|
285
|
+
Returns:
|
286
|
+
Configuration info dictionary
|
287
|
+
"""
|
751
288
|
try:
|
752
|
-
client_info = self.client.get_client_info()
|
753
|
-
|
289
|
+
client_info = self.client.get_client_info() if self._client else {}
|
290
|
+
|
754
291
|
return {
|
755
292
|
"configured": self.is_configured,
|
756
293
|
"provider": client_info.get("provider", "unknown"),
|
757
|
-
"cache_size": len(self.
|
294
|
+
"cache_size": len(self.translation_cache._cache) if hasattr(self.translation_cache, '_cache') else 0,
|
758
295
|
"client_info": client_info,
|
759
|
-
"supported_languages": list(self.language_names.keys()),
|
296
|
+
"supported_languages": list(self.text_utils.language_names.keys()),
|
760
297
|
}
|
761
298
|
except Exception as e:
|
762
299
|
logger.error(f"Failed to get config info: {e}")
|
@@ -766,20 +303,30 @@ Return ONLY the JSON with translated VALUES and original English keys:"""
|
|
766
303
|
}
|
767
304
|
|
768
305
|
@classmethod
|
769
|
-
def send_translation_alert(
|
770
|
-
|
306
|
+
def send_translation_alert(
|
307
|
+
cls,
|
308
|
+
message: str,
|
309
|
+
context: Optional[Dict[str, Any]] = None
|
310
|
+
) -> None:
|
311
|
+
"""
|
312
|
+
Send translation alert via configured notification services.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
message: Alert message
|
316
|
+
context: Optional context data
|
317
|
+
"""
|
771
318
|
try:
|
772
319
|
# Try to send via Telegram if available
|
773
320
|
from django_cfg.modules.django_telegram import DjangoTelegram
|
774
321
|
telegram = DjangoTelegram()
|
775
|
-
|
322
|
+
|
776
323
|
text = f"🌐 <b>Translation Alert</b>\n\n{message}"
|
777
324
|
if context:
|
778
325
|
text += "\n\n<b>Context:</b>\n"
|
779
326
|
for key, value in context.items():
|
780
327
|
text += f"• {key}: {value}\n"
|
781
|
-
|
328
|
+
|
782
329
|
telegram.send_message(text, parse_mode="HTML", fail_silently=True)
|
783
|
-
|
330
|
+
|
784
331
|
except Exception as e:
|
785
332
|
logger.error(f"Failed to send translation alert: {e}")
|