= Setting a translation = == Assumptions == We're not setting a translation that's identical to the current one. We've checked that lock_timestamp is newer than the last update date. Messages have been validated against gettext (otherwise you wouldn't be setting them as current). A diverged message cannot be both the current Ubuntu message and the current upstream message. Those would be diverged in different templates. When we look for an "identical" message, we look for one that is either shared, or diverged in the same template that we're looking at. We completely ignore messages that are diverged to other templates. == Guiding principles == There are two ''sides'' to translation: the Ubuntu side and the upstream side. A POFile is always either on the one side or on the other, though it can share translations with the other side. Each side can have its own shared translations, and each POFile can have its own diverged translations. For a given POTMsgSet in a given POFile, its relationship with the other translation side can be in one of three states: 1. '''Diverged''': the current translation in the POFile is diverged from the shared translation for the POFile's own side. New translations should stay diverged, unless they match existing shared translations (in which case the translation converges with the matching existing translation). So `setCurrentTranslation` may end or maintain a divergence, but never introduce a new one. It may however create a diverged clone of an existing translation message in order to maintain a divergence. 1. '''Shared''': the current translation is shared with other POFiles on the POFile's own side—but not with the other side. New translations should stay shared on the POFile's own side, but only converge with those on the other side if the new translation is identical to that side's shared translation. So `setCurrentTranslation` may keep the translation shared, or bring it into the "tracking" state described below. 1. '''Tracking''': the POFile's shared translation is also the other side's shared translation (including the case where neither side has a shared translation). The other side may still have diverged messages, as may other POFiles on the POFile's own side, but those are not relevant here. The "merging policy" determines whether a new translation should also be shared on the other side. So `setCurrentTranslation` may break the tracking or maintain it, but never cause divergence. '''(NEW/CHANGED)''' Exception: In the shared state, if the other side has no shared translation and the merging policy allows it, `setCurrentTranslation` steals the other side's flag (and thus moves the message to the tracking state). == What happens to the existing current message == A: Deactivate & converge. {{{ If there is already a shared message identical to current: current.delete() else: current.is_current = False current.potemplate = None }}} B: Deactivate. {{{ current.is_current = False }}} Z: Do nothing. There is no incumbent message. == What happens to the new current message == Some numbers are unused because their scenarios have collapsed into other ones. 1. Create & activate. {{{ create TM 'new' new.is_current = True }}} 2. Create, diverge, activate. {{{ create TM 'new' new.is_current = True new.potemplate = current.potemplate }}} 4. Activate. {{{ new.is_current = True }}} 5. '''(NEW/CHANGED)''' If other is not current, it's a suggestion. Diverge & activate. (If it is current, deactivating the old diverged current message has already unmasked it). {{{ if not new.is_current: (2) }}} 6. '''(NEW/CHANGED)''' If other is not already current, fork a diverged message. (If it is current, deactivating the old diverged current message has already unmasked it). {{{ if not new.is_current: new.potemplate = current.potemplate new.is_current = True }}} 7. Converge & activate. {{{ new.potemplate = None # (watch for existing identical TM) new.is_current = True }}} == Capture the Flag == Look for a shared translation with the `is_other` flag set: '`other`'. *. If the merging policy says so and the other side is untranslated, assume the other flag as well: {{{ if other is None: new.is_other = True }}} ⊕. Tracking. If the merging policy says so, steal the other flag: {{{ other.is_other = False new.is_other = True }}} By default, the policy should approve the change on is_other if working on upstream; ''or'' if working on Ubuntu but with privileges to edit both. == Execution matrix == Parenthesized elements are no-ops in their specific situations. ||<|2-2#E0E0E0> ||<-6:> IDENTICAL EXISTING TM 'new' || || None || shared || diverged || upstream shared || ||<|4>CURRENT || None || Z1* || Z4* || Z7* || Z4(*) || || shared || B1* || B4* || B7* || B4(*) || || diverged || A2 || A5 || A4 || A6 || || upstream shared || B1⊕ || B4⊕ || B7⊕ || (B4⊕) || == Notes == A diverged message can mask a similar shared message as well. We tried to take this into account throughout, and believe we covered it. Conditionals in the numbered parts ("what happens to the new current message") may warrant splitting rows or columns later. Karma is being handled. == Implementation notes == See [[https://code.launchpad.net/~jtv/launchpad/recife-552639/+merge/24883|lp:~jtv/launchpad/recife-552639]] This implements two methods: one for setting the upstream translation, and one for setting the Ubuntu translation. An upstream translation also overrides Ubuntu translations; whether the converse also happens when translating in Ubuntu is determined by an optional argument. The code calls the "current" message the "incumbent," and the "existing" message the "twin." If the merging policy approves, the newly activated instruction will also receive the "other flag" if there is no current translation to steal it from.