Skip to content

Feature/per translator review - #20370

Open
Harsha-032 wants to merge 7 commits into
WeblateOrg:mainfrom
Harsha-032:feature/per-translator-review
Open

Feature/per translator review#20370
Harsha-032 wants to merge 7 commits into
WeblateOrg:mainfrom
Harsha-032:feature/per-translator-review

Conversation

@Harsha-032

Copy link
Copy Markdown
Contributor

This PR implements per-translator review tracking, fulfilling the request to explicitly list which users have approved a translation, rather than relying on a binary anonymous "Approved" toggle.
A new UnitReview model has been introduced to track individual approvals. When a user approves a string, a badge reading Reviewed by: [username] will be displayed above the source string. If the translation is modified, all existing reviews for that unit are automatically cleared so it can be re-reviewed.
This also adds support for the reviewed_by:me and reviewed_by:[username] search queries.

Changes Made

  • Added UnitReview model and corresponding migration to track user reviews per translation unit.
  • Updated Unit.translate() to save a UnitReview record when STATE_APPROVED is selected, and to clear reviews when the translation text changes.
  • Updated the translate.html template to render the Reviewed by: [username] badge.
  • Added reviewed_by: search keyword support and tests.
  • Updated docs/changes.rst with the new feature.

UI Changes

Before:
Previously, the review process was completely anonymous. If translation reviews were turned on, clicking "Approved" would simply set the state to Approved, but there was no visual indicator of who actually reviewed the translation on the UI, nor was there a way to track multiple reviewers.
After:
image

Testing Performed

  • Validated that STATE_APPROVED properly records the review.
  • Confirmed multiple users approving the same string displays multiple reviewers.
  • Confirmed editing the translation automatically clears all existing UnitReview records.
  • Tested search queries reviewed_by:me to successfully filter approved strings.
  • Ran Ruff format and check successfully.

@Harsha-032
Harsha-032 requested a review from nijel as a code owner June 30, 2026 03:13
@Harsha-032

Copy link
Copy Markdown
Contributor Author

@nijel can you review this for the issue #2699

@nijel nijel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this does something different from what #2699 asked. My understanding there is that the motivation there is for each translator to have it's own review state.

While this pull request implements secondary tracking of who did the last state change to approved (effectively duplicating what can be figured out from the Change model already).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These files should not be here.


class Meta:
app_label = "trans"
unique_together = [("unit", "user")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ruff is failing here, but you claim that you've run it successfully.

def is_reviewed_by(self, user: User) -> bool:
if not user.is_authenticated:
return False
return self.reviews.filter(user=user).exists()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used anywhere?

if self.state == STATE_APPROVED:
self.reviews.get_or_create(user=user)
else:
self.reviews.filter(user=user).delete()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will store at most one user in the reviews model. If there is no change, reviews are not updated (so if the string is already approved, no additional approvals will be added).

# Test reviewed_by
self.unit = self.get_unit()
self.unit.reviews.create(user=self.user)
self.do_search({"q": "reviewed_by:me"}, "Hello, world")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests fail here; have you run the test suite?

@Harsha-032

Copy link
Copy Markdown
Contributor Author

@nijel You're completely right—my current implementation just mirrors the global state change rather than tracking independent per-user states.

To properly implement the per-translator review state as requested in #2699, I'd like to align on the architecture before rewriting the logic. My proposed approach would be:

Expand the new UnitReview model to store not just the user, but also the state (e.g., Approved, Needs Editing) that the specific user selected.

Decouple the UI's "Review state" radio buttons from the global Unit.state, so that submitting the form updates the current user's UnitReview record instead.

My main architectural question before proceeding: How should this interact with the global Unit.state? If User A marks their personal state as "Approved", but User B marks theirs as "Needs editing", how should we calculate the global Unit.state? Should we introduce a threshold setting (e.g., requires 2 approvals to globally approve), or should the global state just be removed entirely in favor of an aggregated query?

Let me know your thoughts on the best way to integrate this into the core engine!

@Harsha-032

Copy link
Copy Markdown
Contributor Author

Hey @nijel any suggestions?

@luzpaz

luzpaz commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

soft bump. This is a needed feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants