summaryrefslogtreecommitdiff
path: root/app/controllers/page_translations_controller.rb
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-13 16:36:38 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-13 16:36:38 +0200
commit0b6ae26f3b4c3c8278e569fa6644a544e996c25d (patch)
tree6c0e8dd23fa243e6bf7e6473aebae3f13ea20dea /app/controllers/page_translations_controller.rb
parente2705119ba718d40ff4f29c00027c2dfecef01ce (diff)
Add per-node translation management: list, edit, destroy, compare
Replaces the old locale-switch-and-edit-the-same-screen workflow, which conflated presentation locale with content locale and let an editor silently drift into editing the wrong language with no persistent signal that anything had changed. Non-default-locale content now has its own explicit routes and screens, never sharing a route param with the ambient chrome locale. - PageTranslationsController: index/show/edit/update/destroy, scoped to Page.non_default_locales; update handles first-time creation too, so there's no separate new/create step. - Reads go through the actual PageTranslation row (Page#translation_summary), never through the Globalize fallback-bearing accessor -- fallback is correct for public rendering but wrong for editing, where a missing translation needs to look empty, not borrowed from another locale. - Translations ride on the page's own draft/head cycle; no independent publish state. - nodes#show gains a Translations section (per-locale Lock+Edit / Create+Lock, Destroy, a link into the read-only Compare view) and a locale indicator on its own default-locale content; nodes#edit, nodes#update, and nodes#autosave are pinned to the default locale via Globalize.with_locale regardless of the ambient route locale. - nodes#show no longer double-loads the node or calls wipe_draft! on every view (see previous commit for why that's now safe). - .preview_link_row is renamed .aligned_action_row now that it has a second real consumer.
Diffstat (limited to 'app/controllers/page_translations_controller.rb')
-rw-r--r--app/controllers/page_translations_controller.rb98
1 files changed, 98 insertions, 0 deletions
diff --git a/app/controllers/page_translations_controller.rb b/app/controllers/page_translations_controller.rb
new file mode 100644
index 00000000..47f01f7d
--- /dev/null
+++ b/app/controllers/page_translations_controller.rb
@@ -0,0 +1,98 @@
1class PageTranslationsController < ApplicationController
2 layout 'admin'
3
4 before_action :login_required
5 before_action :find_node
6 before_action :find_locale, :only => [:show, :edit, :update, :destroy]
7
8 def index
9 page = @node.draft || @node.head
10 @translations = page ? page.translation_summary : []
11 end
12
13 def show
14 @page = @node.draft || @node.head
15 @translation = @page.translations.find_by(:locale => @locale)
16 @default_translation = @page.translations.find_by(:locale => I18n.default_locale)
17 end
18
19 def edit
20 @node.lock_for_editing!(current_user)
21 @page = @node.draft || @node.head
22 @translation = @page.translations.find_by(:locale => @locale)
23 rescue LockedByAnotherUser => e
24 flash[:error] = e.message
25 redirect_to node_path(@node)
26 end
27
28 def update
29 draft = ensure_editable_draft
30 Globalize.with_locale(@locale) { draft.update!(translation_params) }
31 flash[:notice] = "#{@locale.upcase} translation saved. Publish the draft to make it live."
32
33 if params[:commit] == "Save + Unlock + Exit"
34 @node.unlock!
35 redirect_to node_path(@node)
36 else
37 redirect_to edit_node_translation_path(@node, @locale)
38 end
39 rescue LockedByAnotherUser => e
40 flash[:error] = e.message
41 redirect_to node_path(@node)
42 end
43
44 def destroy
45 base = @node.draft || @node.head
46 unless base && base.translated_locales.include?(@locale)
47 flash[:error] = "No #{@locale.to_s.upcase} translation exists to remove."
48 return redirect_to node_path(@node)
49 end
50
51 if (base.translated_locales - [@locale]).empty?
52 flash[:error] = "Can't remove the only remaining translation."
53 return redirect_to node_path(@node)
54 end
55
56 draft = ensure_editable_draft
57 draft.translations.where(:locale => @locale).delete_all
58 draft.reload
59
60 flash[:notice] = "#{@locale.upcase} translation removed from the draft. Publish to make this permanent."
61 redirect_to node_path(@node)
62 rescue LockedByAnotherUser => e
63 flash[:error] = e.message
64 redirect_to node_path(@node)
65 end
66
67 private
68
69 def find_node
70 @node = Node.find(params[:node_id])
71 end
72
73 # Every remaining action's :translation_locale is already
74 # router-constrained to non-default locales, so this should never
75 # actually fire in practice -- it's a deliberate second check, kept
76 # as a drift detector in case the route constraint and this list
77 # (both driven by Page.non_default_locales) ever disagree.
78 def find_locale
79 candidate = params[:translation_locale].to_s.to_sym
80 unless Page.non_default_locales.include?(candidate)
81 raise ActionController::RoutingError, "Unknown or default locale"
82 end
83 @locale = candidate
84 end
85
86 # Never mutates head directly. Locks first (same guard as the
87 # primary editor), then reuses an existing draft or creates one --
88 # deliberately not going through the autosave buffer: this is a
89 # plain, explicit Save, not a live-typing surface.
90 def ensure_editable_draft
91 @node.lock_for_editing!(current_user)
92 @node.draft || @node.create_new_draft(current_user)
93 end
94
95 def translation_params
96 params.fetch(:page, {}).permit(:title, :abstract, :body)
97 end
98end