django-cfg 1.3.13__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.
Files changed (438) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/user_admin.py +39 -16
  3. django_cfg/apps/accounts/serializers/profile.py +1 -1
  4. django_cfg/apps/accounts/services/otp_service.py +18 -11
  5. django_cfg/apps/accounts/signals.py +15 -24
  6. django_cfg/apps/accounts/utils/notifications.py +217 -358
  7. django_cfg/apps/accounts/views/otp.py +2 -2
  8. django_cfg/apps/accounts/views/webhook.py +1 -1
  9. django_cfg/apps/agents/core/django_agent.py +1 -1
  10. django_cfg/apps/api/commands/views.py +66 -83
  11. django_cfg/apps/api/health/drf_views.py +269 -0
  12. django_cfg/apps/api/health/serializers.py +45 -0
  13. django_cfg/apps/api/health/urls.py +6 -1
  14. django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
  15. django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
  16. django_cfg/apps/knowbase/admin/document_admin.py +136 -270
  17. django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
  18. django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
  19. django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
  20. django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
  21. django_cfg/apps/knowbase/config/constance_fields.py +1 -1
  22. django_cfg/apps/knowbase/config/settings.py +2 -2
  23. django_cfg/apps/knowbase/mixins/__init__.py +19 -2
  24. django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
  25. django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
  26. django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
  27. django_cfg/apps/knowbase/mixins/creator.py +10 -10
  28. django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
  29. django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
  30. django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
  31. django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
  32. django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
  33. django_cfg/apps/knowbase/mixins/service.py +2 -2
  34. django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
  35. django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
  36. django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
  37. django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
  38. django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
  39. django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
  40. django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
  41. django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
  42. django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
  43. django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
  44. django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
  45. django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
  46. django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
  47. django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
  48. django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
  49. django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
  50. django_cfg/apps/knowbase/services/embedding/models.py +18 -14
  51. django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
  52. django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
  53. django_cfg/apps/leads/tests.py +1 -1
  54. django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
  55. django_cfg/apps/payments/admin/balance_admin.py +1 -1
  56. django_cfg/apps/payments/admin/currencies_admin.py +1 -1
  57. django_cfg/apps/payments/admin/payments_admin.py +1 -1
  58. django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
  59. django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
  60. django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
  61. django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
  62. django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
  63. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
  64. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
  65. django_cfg/apps/payments/admin_interface/views/base.py +29 -2
  66. django_cfg/apps/payments/apps.py +1 -1
  67. django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
  68. django_cfg/apps/payments/config/helpers.py +3 -2
  69. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
  70. django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
  71. django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
  72. django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
  73. django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
  74. django_cfg/apps/payments/management/commands/test_providers.py +1 -1
  75. django_cfg/apps/payments/middleware/api_access.py +1 -1
  76. django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
  77. django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
  78. django_cfg/apps/payments/models/balance.py +2 -2
  79. django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
  80. django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
  81. django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
  82. django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
  83. django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
  84. django_cfg/apps/payments/models/payments.py +2 -2
  85. django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
  86. django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
  87. django_cfg/apps/payments/services/core/base.py +1 -1
  88. django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
  89. django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
  90. django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
  91. django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
  92. django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
  93. django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
  94. django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
  95. django_cfg/apps/payments/services/core/payment_service.py +124 -612
  96. django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
  97. django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
  98. django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
  99. django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
  100. django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
  101. django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
  102. django_cfg/apps/payments/services/providers/base.py +1 -1
  103. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
  104. django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
  105. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
  106. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
  107. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
  108. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
  109. django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
  110. django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
  111. django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
  112. django_cfg/apps/payments/services/providers/registry.py +1 -1
  113. django_cfg/apps/payments/services/providers/sync_service.py +1 -1
  114. django_cfg/apps/payments/signals/__init__.py +1 -1
  115. django_cfg/apps/payments/signals/api_key_signals.py +1 -1
  116. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  117. django_cfg/apps/payments/signals/payment_signals.py +1 -1
  118. django_cfg/apps/payments/signals/subscription_signals.py +1 -1
  119. django_cfg/apps/payments/views/api/api_keys.py +1 -1
  120. django_cfg/apps/payments/views/api/balances.py +1 -1
  121. django_cfg/apps/payments/views/api/base.py +1 -1
  122. django_cfg/apps/payments/views/api/currencies.py +1 -1
  123. django_cfg/apps/payments/views/api/payments.py +1 -1
  124. django_cfg/apps/payments/views/api/subscriptions.py +1 -1
  125. django_cfg/apps/payments/views/api/webhooks.py +1 -1
  126. django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
  127. django_cfg/apps/payments/views/serializers/balances.py +1 -1
  128. django_cfg/apps/payments/views/serializers/currencies.py +1 -1
  129. django_cfg/apps/payments/views/serializers/payments.py +1 -1
  130. django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
  131. django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
  132. django_cfg/apps/support/admin/support_admin.py +21 -13
  133. django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
  134. django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
  135. django_cfg/apps/support/utils/support_email_service.py +1 -1
  136. django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
  137. django_cfg/apps/tasks/utils/simulator.py +1 -1
  138. django_cfg/apps/tasks/views/dashboard.py +33 -3
  139. django_cfg/apps/urls.py +5 -1
  140. django_cfg/cli/README.md +57 -471
  141. django_cfg/cli/commands/create_project.py +140 -529
  142. django_cfg/cli/main.py +13 -10
  143. django_cfg/core/__init__.py +63 -6
  144. django_cfg/core/base/__init__.py +5 -0
  145. django_cfg/core/base/config_model.py +652 -0
  146. django_cfg/core/builders/__init__.py +11 -0
  147. django_cfg/core/builders/apps_builder.py +258 -0
  148. django_cfg/core/builders/middleware_builder.py +115 -0
  149. django_cfg/core/builders/security_builder.py +96 -0
  150. django_cfg/core/config.py +20 -892
  151. django_cfg/core/constants.py +69 -0
  152. django_cfg/core/environment/__init__.py +9 -0
  153. django_cfg/core/exceptions.py +45 -298
  154. django_cfg/core/generation/__init__.py +51 -0
  155. django_cfg/core/generation/core_generators/__init__.py +0 -0
  156. django_cfg/core/generation/core_generators/settings.py +90 -0
  157. django_cfg/core/generation/core_generators/static.py +82 -0
  158. django_cfg/core/generation/core_generators/templates.py +141 -0
  159. django_cfg/core/generation/data_generators/__init__.py +15 -0
  160. django_cfg/core/generation/data_generators/cache.py +132 -0
  161. django_cfg/core/generation/data_generators/database.py +117 -0
  162. django_cfg/core/generation/generation.py +92 -0
  163. django_cfg/core/generation/integration_generators/__init__.py +21 -0
  164. django_cfg/core/generation/integration_generators/api.py +237 -0
  165. django_cfg/core/generation/integration_generators/sessions.py +65 -0
  166. django_cfg/core/generation/integration_generators/tailwind.py +54 -0
  167. django_cfg/core/generation/integration_generators/tasks.py +92 -0
  168. django_cfg/core/generation/integration_generators/third_party.py +144 -0
  169. django_cfg/core/generation/orchestrator.py +285 -0
  170. django_cfg/core/generation/protocols.py +30 -0
  171. django_cfg/core/generation/security_generators/__init__.py +0 -0
  172. django_cfg/core/generation/utility_generators/__init__.py +24 -0
  173. django_cfg/core/generation/utility_generators/email.py +58 -0
  174. django_cfg/core/generation/utility_generators/i18n.py +66 -0
  175. django_cfg/core/generation/utility_generators/limits.py +58 -0
  176. django_cfg/core/generation/utility_generators/logging.py +66 -0
  177. django_cfg/core/generation/utility_generators/security.py +101 -0
  178. django_cfg/core/generation/utils/__init__.py +0 -0
  179. django_cfg/core/generation/utils/helpers.py +32 -0
  180. django_cfg/core/integration/__init__.py +18 -25
  181. django_cfg/core/integration/display/startup.py +146 -133
  182. django_cfg/core/integration/url_integration.py +13 -2
  183. django_cfg/core/services/__init__.py +5 -0
  184. django_cfg/core/services/config_service.py +121 -0
  185. django_cfg/core/state/__init__.py +9 -0
  186. django_cfg/core/state/registry.py +84 -0
  187. django_cfg/core/types/__init__.py +15 -0
  188. django_cfg/core/types/aliases.py +15 -0
  189. django_cfg/core/types/enums.py +49 -0
  190. django_cfg/dashboard/DEBUG_README.md +105 -0
  191. django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
  192. django_cfg/dashboard/__init__.py +24 -0
  193. django_cfg/dashboard/components.py +308 -0
  194. django_cfg/dashboard/debug.py +176 -0
  195. django_cfg/dashboard/management/__init__.py +0 -0
  196. django_cfg/dashboard/management/commands/__init__.py +0 -0
  197. django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
  198. django_cfg/dashboard/sections/__init__.py +1 -0
  199. django_cfg/dashboard/sections/base.py +128 -0
  200. django_cfg/dashboard/sections/commands.py +32 -0
  201. django_cfg/dashboard/sections/overview.py +394 -0
  202. django_cfg/dashboard/sections/stats.py +48 -0
  203. django_cfg/dashboard/sections/system.py +73 -0
  204. django_cfg/management/commands/check_settings.py +6 -2
  205. django_cfg/management/commands/clear_constance.py +6 -1
  206. django_cfg/management/commands/create_token.py +5 -4
  207. django_cfg/management/commands/generate.py +5 -0
  208. django_cfg/management/commands/list_urls.py +7 -2
  209. django_cfg/management/commands/migrate_all.py +6 -2
  210. django_cfg/management/commands/migrator.py +6 -1
  211. django_cfg/management/commands/rundramatiq.py +6 -1
  212. django_cfg/management/commands/rundramatiq_simulator.py +11 -4
  213. django_cfg/management/commands/runserver_ngrok.py +9 -7
  214. django_cfg/management/commands/script.py +25 -21
  215. django_cfg/management/commands/show_config.py +6 -1
  216. django_cfg/management/commands/show_urls.py +8 -3
  217. django_cfg/management/commands/superuser.py +5 -4
  218. django_cfg/management/commands/task_clear.py +8 -3
  219. django_cfg/management/commands/task_status.py +8 -3
  220. django_cfg/management/commands/test_email.py +6 -1
  221. django_cfg/management/commands/test_telegram.py +6 -1
  222. django_cfg/management/commands/test_twilio.py +6 -1
  223. django_cfg/management/commands/tree.py +7 -4
  224. django_cfg/models/__init__.py +88 -3
  225. django_cfg/models/api/__init__.py +27 -0
  226. django_cfg/models/{api.py → api/config.py} +1 -1
  227. django_cfg/models/api/drf/__init__.py +21 -0
  228. django_cfg/models/api/drf/config.py +101 -0
  229. django_cfg/models/api/drf/redoc.py +31 -0
  230. django_cfg/models/api/drf/spectacular.py +129 -0
  231. django_cfg/models/api/drf/swagger.py +59 -0
  232. django_cfg/models/{api_keys.py → api/keys.py} +16 -6
  233. django_cfg/models/{limits.py → api/limits.py} +0 -1
  234. django_cfg/models/base/__init__.py +14 -0
  235. django_cfg/models/django/__init__.py +16 -0
  236. django_cfg/models/{constance.py → django/constance.py} +1 -1
  237. django_cfg/models/{environment.py → django/environment.py} +1 -1
  238. django_cfg/models/infrastructure/__init__.py +17 -0
  239. django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
  240. django_cfg/models/infrastructure/database/__init__.py +22 -0
  241. django_cfg/models/infrastructure/database/config.py +265 -0
  242. django_cfg/models/infrastructure/database/converters.py +91 -0
  243. django_cfg/models/infrastructure/database/parsers.py +96 -0
  244. django_cfg/models/infrastructure/database/routing.py +85 -0
  245. django_cfg/models/infrastructure/database/validators.py +170 -0
  246. django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
  247. django_cfg/models/{security.py → infrastructure/security.py} +2 -2
  248. django_cfg/models/ngrok/__init__.py +11 -0
  249. django_cfg/models/ngrok/auth.py +37 -0
  250. django_cfg/models/ngrok/config.py +77 -0
  251. django_cfg/models/ngrok/tunnel.py +35 -0
  252. django_cfg/models/payments/__init__.py +20 -0
  253. django_cfg/models/payments/api_keys.py +57 -0
  254. django_cfg/models/{payments.py → payments/config.py} +56 -154
  255. django_cfg/models/payments/providers/__init__.py +15 -0
  256. django_cfg/models/payments/providers/base.py +25 -0
  257. django_cfg/models/payments/providers/nowpayments.py +48 -0
  258. django_cfg/models/services/__init__.py +18 -0
  259. django_cfg/models/services/base.py +65 -0
  260. django_cfg/models/{email.py → services/email.py} +1 -1
  261. django_cfg/models/services/telegram.py +172 -0
  262. django_cfg/models/tasks/__init__.py +51 -0
  263. django_cfg/models/tasks/backends.py +250 -0
  264. django_cfg/models/tasks/config.py +314 -0
  265. django_cfg/models/tasks/utils.py +174 -0
  266. django_cfg/modules/base.py +18 -3
  267. django_cfg/modules/django_admin/decorators/actions.py +1 -1
  268. django_cfg/modules/django_admin/decorators/display.py +1 -1
  269. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
  270. django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
  271. django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
  272. django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
  273. django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
  274. django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
  275. django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
  276. django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
  277. django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
  278. django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
  279. django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
  280. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
  281. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
  282. django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
  283. django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
  284. django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
  285. django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
  286. django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
  287. django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
  288. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
  289. django_cfg/modules/django_drf_theme/README.md +207 -0
  290. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
  291. django_cfg/modules/django_drf_theme/__init__.py +23 -0
  292. django_cfg/modules/django_drf_theme/apps.py +15 -0
  293. django_cfg/modules/django_drf_theme/renderers.py +58 -0
  294. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
  295. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
  296. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
  297. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
  298. django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
  299. django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
  300. django_cfg/modules/django_email/__init__.py +14 -0
  301. django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
  302. django_cfg/modules/django_email/utils.py +40 -0
  303. django_cfg/modules/django_health/__init__.py +9 -0
  304. django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
  305. django_cfg/modules/django_llm/llm/client.py +155 -550
  306. django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
  307. django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
  308. django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
  309. django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
  310. django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
  311. django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
  312. django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
  313. django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
  314. django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
  315. django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
  316. django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
  317. django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
  318. django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
  319. django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
  320. django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
  321. django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
  322. django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
  323. django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
  324. django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
  325. django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
  326. django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
  327. django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
  328. django_cfg/modules/django_llm/translator/translator.py +150 -603
  329. django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
  330. django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
  331. django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
  332. django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
  333. django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
  334. django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
  335. django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
  336. django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
  337. django_cfg/modules/django_logging/__init__.py +14 -0
  338. django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
  339. django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
  340. django_cfg/modules/django_ngrok/__init__.py +39 -0
  341. django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
  342. django_cfg/modules/django_rpc_old/POETRY.md +344 -0
  343. django_cfg/modules/django_rpc_old/README.md +397 -0
  344. django_cfg/modules/django_rpc_old/TESTING.md +358 -0
  345. django_cfg/modules/django_rpc_old/__init__.py +39 -0
  346. django_cfg/modules/django_rpc_old/client.py +531 -0
  347. django_cfg/modules/django_rpc_old/config.py +279 -0
  348. django_cfg/modules/django_rpc_old/exceptions.py +172 -0
  349. django_cfg/modules/django_tailwind/README.md +478 -0
  350. django_cfg/modules/django_tailwind/__init__.py +7 -0
  351. django_cfg/modules/django_tailwind/apps.py +10 -0
  352. django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
  353. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
  354. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
  355. django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
  356. django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
  357. django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
  358. django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
  359. django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
  360. django_cfg/modules/django_tasks/__init__.py +29 -0
  361. django_cfg/modules/django_tasks/factory.py +127 -0
  362. django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
  363. django_cfg/modules/django_tasks/settings.py +107 -0
  364. django_cfg/modules/django_telegram/__init__.py +29 -0
  365. django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
  366. django_cfg/modules/django_telegram/utils.py +62 -0
  367. django_cfg/modules/django_twilio/__init__.py +54 -107
  368. django_cfg/modules/django_twilio/_imports.py +30 -0
  369. django_cfg/modules/django_twilio/base.py +192 -0
  370. django_cfg/modules/django_twilio/email_otp.py +227 -0
  371. django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
  372. django_cfg/modules/django_twilio/simple_service.py +1 -2
  373. django_cfg/modules/django_twilio/sms.py +94 -0
  374. django_cfg/modules/django_twilio/twilio_service.py +2 -3
  375. django_cfg/modules/django_twilio/unified.py +310 -0
  376. django_cfg/modules/django_twilio/utils.py +190 -0
  377. django_cfg/modules/django_twilio/whatsapp.py +137 -0
  378. django_cfg/modules/django_unfold/callbacks/base.py +198 -7
  379. django_cfg/modules/django_unfold/callbacks/main.py +102 -10
  380. django_cfg/modules/django_unfold/dashboard.py +65 -43
  381. django_cfg/modules/django_unfold/models/config.py +13 -12
  382. django_cfg/modules/django_unfold/models/navigation.py +8 -3
  383. django_cfg/modules/django_unfold/models/tabs.py +2 -2
  384. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
  385. django_cfg/registry/core.py +24 -26
  386. django_cfg/registry/modules.py +5 -2
  387. django_cfg/registry/services.py +20 -3
  388. django_cfg/registry/third_party.py +8 -8
  389. django_cfg/static/admin/css/dashboard.css +260 -0
  390. django_cfg/static/admin/js/commands.js +171 -0
  391. django_cfg/static/admin/js/dashboard.js +126 -0
  392. django_cfg/templates/admin/components/management_commands.js +375 -0
  393. django_cfg/templates/admin/components/progress_bar.html +18 -23
  394. django_cfg/templates/admin/index.html +48 -20
  395. django_cfg/templates/admin/index_new.html +106 -0
  396. django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
  397. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
  398. django_cfg/templates/admin/sections/commands_section.html +626 -0
  399. django_cfg/templates/admin/sections/overview_section.html +112 -0
  400. django_cfg/templates/admin/sections/stats_section.html +35 -0
  401. django_cfg/templates/admin/sections/system_section.html +99 -0
  402. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
  403. django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
  404. django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
  405. django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
  406. django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
  407. django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
  408. django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
  409. django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
  410. django_cfg/templatetags/django_cfg.py +7 -1
  411. django_cfg/utils/smart_defaults.py +4 -4
  412. django_cfg-1.4.0.dist-info/METADATA +920 -0
  413. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/RECORD +424 -195
  414. django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
  415. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
  416. django_cfg/core/generation.py +0 -621
  417. django_cfg/management/commands/validate_config.py +0 -189
  418. django_cfg/models/database.py +0 -480
  419. django_cfg/models/drf.py +0 -272
  420. django_cfg/models/ngrok.py +0 -122
  421. django_cfg/models/services.py +0 -440
  422. django_cfg/models/tasks.py +0 -550
  423. django_cfg/modules/django_twilio/service.py +0 -942
  424. django_cfg/template_archive/django_sample.zip +0 -0
  425. django_cfg/templates/rest_framework/api.html +0 -12
  426. django_cfg/utils/toolkit.py +0 -703
  427. django_cfg-1.3.13.dist-info/METADATA +0 -1029
  428. /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
  429. /django_cfg/core/{environment.py → environment/detector.py} +0 -0
  430. /django_cfg/models/{cors.py → api/cors.py} +0 -0
  431. /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
  432. /django_cfg/models/{base.py → base/config.py} +0 -0
  433. /django_cfg/models/{cfg.py → base/module.py} +0 -0
  434. /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
  435. /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
  436. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
  437. {django_cfg-1.3.13.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
  438. {django_cfg-1.3.13.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 hashlib
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
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class TranslationError(Exception):
24
- """Base exception for translation-related errors."""
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 Service for django_cfg, configured via DjangoConfig.
36
-
37
- Provides translation functionality with automatic configuration
38
- from the main DjangoConfig instance.
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
- self._translation_cache = {}
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
- # Statistics
71
- self.stats = {
72
- 'total_translations': 0,
73
- 'cache_hits': 0,
74
- 'cache_misses': 0,
75
- 'total_tokens_used': 0,
76
- 'total_cost_usd': 0.0,
77
- 'language_pairs': {},
78
- 'successful_translations': 0,
79
- 'failed_translations': 0
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 using simple heuristics.
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
- if not text or not text.strip():
128
- return 'unknown'
129
-
130
- # Clean text for better detection
131
- cleaned_text = self._clean_text(text)
132
-
133
- if not cleaned_text:
134
- return 'unknown'
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
- if not text or not text.strip():
215
- return False
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
- # Auto-detect source language if needed
310
- if source_language == 'auto':
311
- source_language = self.detect_language(text)
312
- if source_language == 'unknown':
313
- logger.warning(f"Could not detect language for: {text[:50]}...")
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 if temperature is not None else 0.1, # Low temperature for consistent translations
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
- self.stats['failed_translations'] += 1
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 TranslationError(error_msg) from e
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 with automatic language detection.
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
- # Extract translatable texts
397
- translatable_texts = self._extract_translatable_texts(data, source_language, target_language)
398
-
399
- if not translatable_texts:
400
- logger.info("No texts need translation in JSON object")
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
- logger.info(f"Found {len(translatable_texts)} texts to translate")
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
- error_msg = f"Failed to translate JSON: {e}"
417
- logger.error(error_msg)
249
+ logger.error(f"JSON translation failed: {e}")
418
250
  if not fail_silently:
419
- raise TranslationError(error_msg) from e
251
+ raise
420
252
  return data
421
253
 
422
- def _translate_json_batch(self, data: Any, target_language: str, source_language: str = 'auto',
423
- model: Optional[str] = None, temperature: Optional[float] = None,
424
- fail_silently: bool = False) -> Any:
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
- def _detect_language(self, text: str) -> Optional[str]:
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
- def get_stats(self) -> Dict[str, Any]:
736
- """Get translation statistics."""
737
- return self.stats.copy()
260
+ Returns:
261
+ Dictionary with statistics
262
+ """
263
+ return self.stats_tracker.get_stats()
738
264
 
739
265
  def clear_cache(self) -> bool:
740
- """Clear translation cache."""
266
+ """
267
+ Clear translation cache.
268
+
269
+ Returns:
270
+ True if successful
271
+ """
741
272
  try:
742
- self._translation_cache.clear()
743
- self.client.clear_cache()
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
- """Get translation service configuration information."""
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._translation_cache),
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(cls, message: str, context: Optional[Dict[str, Any]] = None) -> None:
770
- """Send translation alert via configured notification services."""
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}")