l10n: AGENTS.md: add quotation mark preservation guidelines

Add a "Preserving Quotation Marks" section to prevent AI-assisted translation and review from incorrectly converting language-specific UTF-8 curly quotes (e.g., „ U+201E, " U+201C for Bulgarian) into ASCII straight quotes " (U+0022), which would cause PO string truncation and syntax errors. Also update the "Special characters" item in the Quality checklist to reference the new section. Signed-off-by: Jiang Xin <worldhello.net@gmail.com>

Jiang Xin committed Jun 26, 2026 at 19:57 UTC 5eb25b9605bd31345e81840d428d9bf019695c4e
1 file changed +50 -1
po/AGENTS.md
+50 -1
@@ -127,6 +127,52 @@ etc.), and quotes exactly as in `msgid`. Only reorder placeholders with
127 positional syntax when needed (see Placeholder Reordering below).
128
129
130 +### Preserving Quotation Marks
131 +
132 +Some languages use language-specific UTF-8 quotation marks (curly/smart
133 +quotes) rather than ASCII straight quotes. **Always preserve these
134 +characters exactly as they appear in the source.** Do **not** convert them
135 +to ASCII straight quotes.
136 +
137 +**Protected quotation marks** (non-exhaustive list):
138 +
139 +| Character | Unicode | Name | Languages |
140 +|-----------|---------|------|-----------|
141 +| „ | U+201E | DOUBLE LOW-9 QUOTATION MARK | Bulgarian, German, etc. |
142 +| " | U+201C | LEFT DOUBLE QUOTATION MARK | Bulgarian, etc. |
143 +| " | U+201D | RIGHT DOUBLE QUOTATION MARK | English, German, etc. |
144 +| ' | U+2018 | LEFT SINGLE QUOTATION MARK | English, etc. |
145 +| ' | U+2019 | RIGHT SINGLE QUOTATION MARK | English, etc. |
146 +| « | U+00AB | LEFT-POINTING DOUBLE ANGLE QUOTATION MARK | French, Russian, etc. |
147 +| » | U+00BB | RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK | French, Russian, etc. |
148 +| ‹ | U+2039 | SINGLE LEFT-POINTING ANGLE QUOTATION MARK | French, etc. |
149 +| › | U+203A | SINGLE RIGHT-POINTING ANGLE QUOTATION MARK | French, etc. |
150 +
151 +**Why this matters in PO files**: In PO file format, the ASCII straight
152 +double quote `"` (U+0022) is the **string delimiter**. If a translation
153 +contains a curly quote that is incorrectly converted to `"` (U+0022),
154 +the PO parser will interpret it as the end of the string, causing:
155 +
156 +1. **String truncation**: The `msgstr` value is cut short at the
157 + spurious quote character.
158 +2. **Syntax errors**: `msgfmt --check` fails with parse errors at
159 + the line where the string was prematurely terminated.
160 +3. **Data loss**: Content after the accidental quote delimiter is
161 + misinterpreted or lost.
162 +
163 +**Rules**:
164 +
165 +- **Never** replace language-specific quotation marks with ASCII
166 + straight quotes `"` (U+0022) or `'` (U+0027).
167 +- Apply this rule when translating PO files, PO multi-line strings,
168 + and GETTEXT JSON `msgstr` array values.
169 +- Apply this rule when generating suggested translations
170 + (`suggest_msgstr`) during review.
171 +- If the source `msgid` uses ASCII straight quotes, preserve them
172 + as-is in the translation unless the target language convention
173 + requires different quotation marks.
174 +
175 +
176 ### Placeholder Reordering
177
178 When reordering placeholders relative to `msgid`, use positional syntax (`%n$`)
@@ -387,7 +433,10 @@ read and write this format.
433 - **Placeholders**: Preserve variables (`%s`, `{name}`, `$1`) exactly; use
434 positional parameters when reordering (see "Placeholder Reordering" above).
435 - **Special characters**: Preserve escape sequences (`\n`, `\"`, `\\`, `\t`),
390 - placeholders exactly as in `msgid`. See "Preserving Special Characters" above.
436 + placeholders exactly as in `msgid`. Preserve language-specific quotation
437 + marks (curly/smart quotes like „, ", ", ', ') — do not convert them to
438 + ASCII straight quotes. See "Preserving Special Characters" and
439 + "Preserving Quotation Marks" above.
440 - **Plurals and gender**: Correct forms and agreement.
441 - **Context fit**: Suitable for UI space, tone, and use (e.g. error vs. tooltip).
442 - **Cultural appropriateness**: No offensive or ambiguous content.