virolea / rosetta

Unobstrusive Internationalization solution for Rails applications.
MIT License
53 stars 1 forks source link

Eager load translations for a given key through the global Rosetta.locale variable in Admin #3

Closed virolea closed 1 week ago

virolea commented 3 weeks ago

Background

The main admin view in Rosetta features a list of all the available keys and their translation counterpart in the selected locale -- should it exist:

CleanShot 2024-09-17 at 16 45 47

In data-terms, it means I need to LEFT JOIN translations on both the translation key and the locale. In SQL it looks like:

SELECT * 
FROM translation_keys tk 
LEFT JOIN translations t on t.translation_key_id = tk.id AND t.locale_id = %{LOCALE_ID}

Where %{LOCALE_ID} is set at request time to the selected locale in the application. To my knowledge, Rails does not allow to eager load records with a custom JOIN. A first implementation of this view added two custom scopes on TranslationKey that retrieved the required data for the view:

https://github.com/virolea/rosetta/blob/3c4508cecc3af142fcc2efe6ffa69e931f15a5ce/app/models/rosetta/translation_key.rb#L7-L15

Every instance of TranslationKey loaded through one of those scopes would feature two additional attributes: translation_id and translation_value, used in the view to implement the required edit feature.

Problem

Straying away from the common path comes with its loads of problems and frustrations. With that setup, the partials consuming the loaded records expect translation_id and translation_value to be present on the TranslationKey model. When rendering the partial for a single record (not the collection, in a turbo stream for instance) it brings design challenges.

Moreovever, having TranslationKey bearing values for the translation model seems wrong. I'd rather handle a separate entity that clearly states the concern.

Solution

In https://github.com/virolea/rosetta/commit/e29d60a1c58c1837a42b1bb4eaa3023a16406db0, the custom scopes have been dropped in favour of custom has_one relationship that properly eager loads the translation model for a given (key, locale) tuple.

https://github.com/virolea/rosetta/blob/e29d60a1c58c1837a42b1bb4eaa3023a16406db0/app/models/rosetta/translation_key.rb#L1-L7

TranslationKey.includes(:translation_in_current_locale) will correctly load the associated records. However it comes with a cost, as it uses the global Rosetta.locale variable. This is necessary since the available locales are saved in the db and only known at runtime. I can't set those up beforehand.

It feels really sharp of a knife, however this is a tradeoff I am willing to accept for now considering the following points: