1111 LEGACY_STYLE_KEYS ,
1212 SEMANTIC_ACCURACY_RULES ,
1313 STRUCTURE_FORMAT_RULES ,
14+ STYLE_DIFFERENTIATION_RULES ,
1415 STYLE_VARIANTS ,
1516 TARGET_LANGUAGES ,
1617 native_editor_instruction ,
@@ -69,6 +70,68 @@ def flagged_invented_ready_subject(source_text: str, result: dict) -> bool:
6970 return any (target_invents_speaker_ready (t ) for t in collect_style_to_texts (result ))
7071
7172
73+ def _style_display_text (pair : dict | None , * , wants_translation : bool ) -> str :
74+ item = pair or {}
75+ if wants_translation :
76+ return (item .get ("to" ) or item .get ("from" ) or "" ).strip ()
77+ return (item .get ("from" ) or item .get ("to" ) or "" ).strip ()
78+
79+
80+ def _norm_style (text : str ) -> str :
81+ return " " .join ((text or "" ).lower ().split ())
82+
83+
84+ def _near_duplicate (a : str , b : str ) -> bool :
85+ if not a or not b :
86+ return False
87+ if a == b :
88+ return True
89+ sa , sb = set (a .split ()), set (b .split ())
90+ if not sa or not sb :
91+ return False
92+ overlap = len (sa & sb ) / len (sa | sb )
93+ shorter = min (len (a ), len (b ))
94+ longer = max (len (a ), len (b ))
95+ if shorter < 24 :
96+ return a == b
97+ return overlap >= 0.9 and shorter / longer >= 0.85
98+
99+
100+ def styles_too_similar_feedback (result : dict , * , wants_translation : bool ) -> str | None :
101+ native = _norm_style (
102+ _style_display_text (result .get ("native" ), wants_translation = wants_translation )
103+ )
104+ friendly = _norm_style (
105+ _style_display_text (result .get ("friendly" ), wants_translation = wants_translation )
106+ )
107+ professional = _norm_style (
108+ _style_display_text (
109+ result .get ("professional" ), wants_translation = wants_translation
110+ )
111+ )
112+ if max (len (native ), len (friendly ), len (professional )) < 24 :
113+ return None
114+ if _near_duplicate (native , friendly ):
115+ return (
116+ "HARD RULE FAILED: Native and Friendly/Casual are identical or nearly "
117+ "identical. Rewrite Friendly/Casual so it is clearly more conversational "
118+ "(Slack/WhatsApp: shorter, contractions, spoken phrasing). Do NOT copy Native. "
119+ "Keep the same meaning and facts. Professional must stay workplace-appropriate."
120+ )
121+ if _near_duplicate (native , professional ):
122+ return (
123+ "HARD RULE FAILED: Native and Professional are identical or nearly "
124+ "identical. Rewrite Professional by restructuring for the workplace "
125+ "(concise, polite, direct). Do NOT only swap synonyms. Keep the same meaning."
126+ )
127+ if _near_duplicate (friendly , professional ):
128+ return (
129+ "HARD RULE FAILED: Friendly/Casual and Professional are nearly identical. "
130+ "Make Friendly more spoken and Professional more workplace. Same meaning."
131+ )
132+ return None
133+
134+
72135def _style_pair (item ) -> dict [str , str ]:
73136 if isinstance (item , str ):
74137 return {"from" : item .strip (), "to" : "" }
@@ -200,16 +263,16 @@ def build_styled_translation_prompt(
200263 f'"from" = natural rewrite of the input in { src_hint } '
201264 "(same enhanced source on all three).\n "
202265 f'"to" = Native / Friendly / Professional in { tgt } ({ loc } ). '
203- "They must not be clones .\n "
266+ "They MUST be different communication styles, not word swaps .\n "
204267 "grammar_notes: JSON array of original/correction/explanation.\n "
205268 "Same who/what/when. Keep source layout. Short input → short output."
206269 )
207270 else :
208271 translation_block = (
209272 f"Stay in { src_hint } / { loc } . "
210273 'Put each version in "from" and set "to" to empty.\n '
211- "Native preserves tone; Friendly is more relaxed ; "
212- "Professional is workplace-human ."
274+ "Native preserves original tone; Friendly is clearly more conversational ; "
275+ "Professional is clearly more workplace. Do not copy Native ."
213276 )
214277
215278 context_block = (
@@ -228,6 +291,9 @@ def build_styled_translation_prompt(
228291 system_prompt = f"""
229292{ editor }
230293
294+ STYLE DIFFERENTIATION (mandatory):
295+ { STYLE_DIFFERENTIATION_RULES }
296+
231297SEMANTIC ACCURACY (mandatory):
232298{ SEMANTIC_ACCURACY_RULES }
233299
@@ -334,6 +400,11 @@ def _once(retry_feedback=None):
334400 "and for pickup use 'come pick it up' / 'come by to collect it' / 'abholen'."
335401 )
336402 )
403+ similar = styles_too_similar_feedback (
404+ result , wants_translation = wants_translation
405+ )
406+ if similar and "error" not in result :
407+ result = _once (retry_feedback = similar )
337408 result ["provider" ] = used_provider
338409 return result
339410 except json .JSONDecodeError :
0 commit comments