{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-10-05T18:31:30Z","object_id":{"id":1410,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_translation","title":"mod_translation","uri":"https:\/\/zotonic.com\/id\/1410"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-10-06T11:52:33Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2567,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_content_management","title":"Content management","uri":"https:\/\/zotonic.com\/id\/2567"},"seq":3},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2588,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_localization_and_translation","title":"Localization and translation","uri":"https:\/\/zotonic.com\/id\/2588"},"seq":4},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2592,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_authorization_and_access_control","title":"Authorization and access control","uri":"https:\/\/zotonic.com\/id\/2592"},"seq":5},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2625,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_model","title":"Model","uri":"https:\/\/zotonic.com\/id\/2625"},"seq":6},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2668,"is_a":["categorization","keyword","keyword_data_type"],"name":"zotonic_topic_language_code","title":"Language code","uri":"https:\/\/zotonic.com\/id\/2668"},"seq":7},{"created":"2026-10-06T11:52:33Z","object_id":{"id":2669,"is_a":["categorization","keyword","keyword_data_type"],"name":"zotonic_topic_translated_text","title":"Translated text","uri":"https:\/\/zotonic.com\/id\/2669"},"seq":8}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":2760,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2760"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_model_model_translation_tree","page_url":{"en":"https:\/\/zotonic.com\/docs\/2760\/translation_tree","x-default":"https:\/\/zotonic.com\/docs\/2760\/translation_tree"},"preview_url":null,"resource":{"version":32,"pivot_location_lat":null,"title":"translation_tree","is_authoritative":true,"body":"<p>Tree translation API. <code>get\/&lt;id&gt;<\/code> returns language counts, <code>get\/status\/&lt;id&gt;<\/code> returns\njob progress and <code>post\/&lt;id&gt;<\/code> starts a job. Reads require edit access to the root;\ntree traversal requires visibility of every member. Each page must still be editable\nwhen the worker reaches it; otherwise it is skipped. Trees include their root and\nunique menu\/haspart descendants. No caller privileges are elevated.<\/p>\n<p>The <code>bulk_dialog<\/code> postback accepts an <code>ids<\/code> list for bulk-update selections. It checks\nedit access to every selected page, deduplicates the IDs, and stores a selection for\none day. Its <code>bulk-<\/code> token can be used instead of a resource ID on the same model\npaths. Only the authenticated user who created the selection can access it. Bulk\njobs process exactly the selected pages, without traversing their descendants, and\ncheck each page&#39;s edit permission again before changing it.<\/p>\n<p>For example, <code>m.translation_tree[123]<\/code> returns the following data for a tree with\nthree unique pages, two available in English and all three available in Dutch:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">#{total =&gt; 3, languages =&gt; [{en, 2}, {nl, 3}]}\n<\/code><\/pre>\n<p>The total includes the root. Language counts can overlap because a page can have\nmultiple languages. The model callback wraps this data as <code>{ok, {Data, Rest}}<\/code>,\nwhere <code>Rest<\/code> is the unused model path.<\/p>\n<p><code>m.translation_tree.status[123]<\/code> can return this progress map:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">#{root =&gt; 123,\n  job =&gt; &lt;&lt;&quot;example-job-id&quot;&gt;&gt;,\n  state =&gt; running,\n  operation =&gt; &lt;&lt;&quot;translate&quot;&gt;&gt;,\n  total =&gt; 3,\n  done =&gt; 2,\n  skipped =&gt; 1,\n  failed =&gt; 0}\n<\/code><\/pre>\n<p><code>done<\/code> counts all processed pages, including skipped and failed pages. <code>total<\/code> is\nzero until traversal finishes. The state becomes <code>complete<\/code> when every page has\nbeen processed, even if individual pages failed; <code>failed<\/code> as a state indicates a\njob-level failure. With no known job the response is <code>#{root =&gt; 123, state =&gt; idle}<\/code>.\nA successful <code>post\/&lt;id&gt;<\/code> returns <code>{ok, Progress}<\/code> as soon as the job starts.<\/p>\n<p>The <code>event\/2<\/code> handler opens the translation dialog using a signed <code>dialog<\/code> postback\nwith an <code>id<\/code> argument identifying the tree root. For example:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% button text=_&quot;Translate all pages&quot;\n    postback={dialog id=tree_id} delegate=&quot;m_translation_tree&quot; %}\n<\/code><\/pre>\n<p>The handler checks edit access to the root and visibility of the tree, then renders\n<code>_dialog_translation_tree.tpl<\/code> with the resolved <code>id<\/code> and the language-count map as\n<code>tree<\/code>. If these checks fail, it displays an error growl. Opening the dialog does\nnot start a job; the dialog submits a confirmed operation through <code>post\/&lt;id&gt;<\/code>.<\/p>","slug":"translation_tree","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"84ac251c540801073d5e212aaace67a068a3418282291f43339a7e7108e4767f","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"category_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"doc_source_path":"apps\/zotonic_mod_translation\/src\/models\/m_translation_tree.erl","publication_start":"2026-10-05T18:31:30Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_translation\/src\/models\/m_translation_tree.erl","pivot_location_lng":null,"doc_source_kind":"model","name":"doc_model_model_translation_tree","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-10-05T18:31:30Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_translation","publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"652b4eddf9263041a2798f290b6c867343988a3f\n","erlang_module":"m_translation_tree","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-06T13:25:23Z","title_slug":"translation_tree"},"uri":"https:\/\/zotonic.com\/id\/2760","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2760"}},"status":"ok"}