mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
Scripts: Relax rich-text checks in the translation validator
And fix a bunch of pre-existing errors.
This commit is contained in:
@@ -19,9 +19,11 @@ from translation.ts_utils import ( # noqa: E402
|
||||
extract_placeholders,
|
||||
extract_rich_tags,
|
||||
parse_catalog,
|
||||
placeholder_counts_match,
|
||||
placeholders_are_subset,
|
||||
placeholders_match,
|
||||
replace_translation_node,
|
||||
unbalanced_rich_tags,
|
||||
validate_translation,
|
||||
)
|
||||
|
||||
@@ -94,13 +96,21 @@ class TranslationToolTests(unittest.TestCase):
|
||||
self.assertEqual(1, placeholders["qt:%1"])
|
||||
self.assertEqual(1, placeholders["qt:%n"])
|
||||
self.assertEqual(1, placeholders["fmt:{}"])
|
||||
self.assertEqual(1, placeholders["fmt:{0:08X}"])
|
||||
self.assertEqual(1, placeholders["fmt:{0}"])
|
||||
self.assertEqual(1, placeholders["printf:%.1f"])
|
||||
self.assertEqual(1, placeholders["printf:%s"])
|
||||
self.assertEqual(1, placeholders["template:${title}"])
|
||||
self.assertEqual(7, sum(placeholders.values()))
|
||||
self.assertTrue(placeholders_match("{} {}", "{1} {0}"))
|
||||
self.assertFalse(placeholders_match("{} {}", "{0} {0}"))
|
||||
self.assertTrue(placeholders_match("Use {0}, then use {0} again", "Usar {0}"))
|
||||
self.assertFalse(placeholder_counts_match("Use {0}, then use {0} again", "Usar {0}"))
|
||||
self.assertTrue(placeholders_match("Saved at {0:%H:%M} on {0:%Y/%m/%d}", "Guardado {0:%d/%m/%Y}"))
|
||||
self.assertFalse(
|
||||
placeholder_counts_match("Saved at {0:%H:%M} on {0:%Y/%m/%d}", "Guardado {0:%d/%m/%Y}")
|
||||
)
|
||||
self.assertFalse(placeholder_counts_match("{0} {0} {1}", "{0} {1} {1}"))
|
||||
self.assertFalse(placeholders_match("Use %1 and %2", "Usar %1"))
|
||||
self.assertTrue(placeholders_are_subset("{} of %n", "%n"))
|
||||
self.assertFalse(placeholders_are_subset("{} of %n", "%n %1"))
|
||||
self.assertEqual({"qt:%1": 1}, dict(extract_placeholders("%1x")))
|
||||
@@ -108,7 +118,12 @@ class TranslationToolTests(unittest.TestCase):
|
||||
def test_rich_text_ignores_angle_bracket_labels(self) -> None:
|
||||
self.assertFalse(extract_rich_tags("<Parent Directory>"))
|
||||
self.assertEqual({"strong": 1, "/strong": 1}, dict(extract_rich_tags("<strong>Text</strong>")))
|
||||
self.assertTrue(validate_translation("<strong>Text</strong>", "テキスト"))
|
||||
self.assertFalse(validate_translation("<strong>Text</strong>", "テキスト"))
|
||||
|
||||
def test_rich_text_tag_pairs_are_balanced(self) -> None:
|
||||
self.assertFalse(unbalanced_rich_tags("<html><head/><body><br><hr/></body></html>"))
|
||||
self.assertFalse(unbalanced_rich_tags("<strong><b>Text</strong></b>"))
|
||||
self.assertEqual({"p": (2, 1), "strong": (1, 0)}, unbalanced_rich_tags("<p><p><strong>Text</p>"))
|
||||
|
||||
def test_fingerprint_ignores_translation_changes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
@@ -256,6 +271,24 @@ class TranslationToolTests(unittest.TestCase):
|
||||
self.assertIn(f"{catalog}:{expected_line}:", result.stdout)
|
||||
self.assertIn("[source: alpha.cpp:1]", result.stdout)
|
||||
|
||||
def test_validation_warns_for_placeholder_count_mismatch(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
catalog = Path(directory) / "catalog.ts"
|
||||
catalog.write_text(
|
||||
FIXTURE.replace(
|
||||
"Hello %1 {0} ${title} %.1f <strong>world</strong>",
|
||||
"Use {0}, then use {0} again",
|
||||
).replace(
|
||||
'<translation type="unfinished"></translation>',
|
||||
"<translation>Usar {0}</translation>",
|
||||
1,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = self.run_tool("validate_ts.py", catalog, "--placeholders-only")
|
||||
self.assertIn("WARNING:", result.stdout)
|
||||
self.assertIn("placeholder count mismatch", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -152,7 +152,10 @@ def extract_placeholders(text: str) -> collections.Counter[str]:
|
||||
if any(match.start() < end and match.end() > start for start, end in occupied):
|
||||
continue
|
||||
occupied.append(match.span())
|
||||
tokens.append(f"{kind}:{match.group(0)}")
|
||||
placeholder = match.group(0)
|
||||
if kind == "fmt" and ":" in placeholder:
|
||||
placeholder = f"{placeholder.split(':', 1)[0]}}}"
|
||||
tokens.append(f"{kind}:{placeholder}")
|
||||
return collections.Counter(tokens)
|
||||
|
||||
|
||||
@@ -174,9 +177,12 @@ def placeholders_match(source: str, translation: str) -> bool:
|
||||
translated_non_fmt = collections.Counter(
|
||||
token for token in translated_tokens.elements() if not token.startswith("fmt:")
|
||||
)
|
||||
if source_non_fmt != translated_non_fmt:
|
||||
if set(source_non_fmt) != set(translated_non_fmt):
|
||||
return False
|
||||
|
||||
if set(source_fmt) == set(translated_fmt):
|
||||
return True
|
||||
|
||||
# Qt's fmt usage permits translators to number anonymous plain placeholders
|
||||
# when reordering arguments, e.g. "{} {}" -> "{1} {0}".
|
||||
if set(source_fmt) == {"{}"}:
|
||||
@@ -185,13 +191,14 @@ def placeholders_match(source: str, translation: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def placeholders_are_subset(source: str, translation: str) -> bool:
|
||||
"""Allow a plural form to omit redundant arguments without adding any."""
|
||||
if placeholders_match(source, translation):
|
||||
return True
|
||||
def placeholder_counts_match(source: str, translation: str) -> bool:
|
||||
"""Return whether compatible placeholders occur the same number of times."""
|
||||
if not placeholders_match(source, translation):
|
||||
return False
|
||||
|
||||
source_tokens = extract_placeholders(source)
|
||||
translated_tokens = extract_placeholders(translation)
|
||||
if translated_tokens <= source_tokens:
|
||||
if source_tokens == translated_tokens:
|
||||
return True
|
||||
|
||||
source_fmt = collections.Counter(
|
||||
@@ -206,7 +213,35 @@ def placeholders_are_subset(source: str, translation: str) -> bool:
|
||||
translated_non_fmt = collections.Counter(
|
||||
token for token in translated_tokens.elements() if not token.startswith("fmt:")
|
||||
)
|
||||
if not translated_non_fmt <= source_non_fmt or set(source_fmt) != {"{}"}:
|
||||
if set(source_fmt) == {"{}"}:
|
||||
fmt_counts_match = sum(source_fmt.values()) == sum(translated_fmt.values())
|
||||
else:
|
||||
fmt_counts_match = source_fmt == translated_fmt
|
||||
return source_non_fmt == translated_non_fmt and fmt_counts_match
|
||||
|
||||
|
||||
def placeholders_are_subset(source: str, translation: str) -> bool:
|
||||
"""Allow a plural form to omit redundant arguments without adding any."""
|
||||
if placeholders_match(source, translation):
|
||||
return True
|
||||
source_tokens = extract_placeholders(source)
|
||||
translated_tokens = extract_placeholders(translation)
|
||||
if set(translated_tokens) <= set(source_tokens):
|
||||
return True
|
||||
|
||||
source_fmt = collections.Counter(
|
||||
token.removeprefix("fmt:") for token in source_tokens.elements() if token.startswith("fmt:")
|
||||
)
|
||||
translated_fmt = collections.Counter(
|
||||
token.removeprefix("fmt:") for token in translated_tokens.elements() if token.startswith("fmt:")
|
||||
)
|
||||
source_non_fmt = collections.Counter(
|
||||
token for token in source_tokens.elements() if not token.startswith("fmt:")
|
||||
)
|
||||
translated_non_fmt = collections.Counter(
|
||||
token for token in translated_tokens.elements() if not token.startswith("fmt:")
|
||||
)
|
||||
if not set(translated_non_fmt) <= set(source_non_fmt) or set(source_fmt) != {"{}"}:
|
||||
return False
|
||||
allowed_numbered = {f"{{{index}}}" for index in range(source_fmt["{}"])}
|
||||
return set(translated_fmt) <= allowed_numbered and all(count == 1 for count in translated_fmt.values())
|
||||
@@ -220,6 +255,23 @@ def extract_rich_tags(text: str) -> collections.Counter[str]:
|
||||
return collections.Counter(tags)
|
||||
|
||||
|
||||
def unbalanced_rich_tags(text: str) -> dict[str, tuple[int, int]]:
|
||||
"""Return paired rich-text elements with unequal opening and closing counts."""
|
||||
opening: collections.Counter[str] = collections.Counter()
|
||||
closing: collections.Counter[str] = collections.Counter()
|
||||
for match in RICH_TAG_RE.finditer(text):
|
||||
tag = match.group(2).lower()
|
||||
if tag in {"br", "hr"} or (not match.group(1) and match.group(0).rstrip().endswith("/>")):
|
||||
continue
|
||||
(closing if match.group(1) else opening)[tag] += 1
|
||||
|
||||
return {
|
||||
tag: (opening[tag], closing[tag])
|
||||
for tag in sorted(opening.keys() | closing.keys())
|
||||
if opening[tag] != closing[tag]
|
||||
}
|
||||
|
||||
|
||||
def validate_translation(source: str, translation: str, allow_missing_placeholders: bool = False) -> list[str]:
|
||||
problems: list[str] = []
|
||||
source_placeholders = extract_placeholders(source)
|
||||
@@ -233,11 +285,6 @@ def validate_translation(source: str, translation: str, allow_missing_placeholde
|
||||
problems.append(
|
||||
f"placeholder mismatch: source={dict(source_placeholders)} translation={dict(translated_placeholders)}"
|
||||
)
|
||||
required_tags = extract_rich_tags(source)
|
||||
translated_tags = extract_rich_tags(translation)
|
||||
missing_tags = required_tags - translated_tags
|
||||
if missing_tags:
|
||||
problems.append(f"missing rich-text tags: {dict(missing_tags)}")
|
||||
return problems
|
||||
|
||||
|
||||
@@ -245,6 +292,10 @@ def extra_rich_tags(source: str, translation: str) -> collections.Counter[str]:
|
||||
return extract_rich_tags(translation) - extract_rich_tags(source)
|
||||
|
||||
|
||||
def missing_rich_tags(source: str, translation: str) -> collections.Counter[str]:
|
||||
return extract_rich_tags(source) - extract_rich_tags(translation)
|
||||
|
||||
|
||||
def message_to_batch_record(message: CatalogMessage) -> dict[str, object]:
|
||||
return {
|
||||
"record_type": "message",
|
||||
|
||||
@@ -17,9 +17,13 @@ from translation.ts_utils import ( # noqa: E402
|
||||
SKIPPED_TYPES,
|
||||
CatalogMessage,
|
||||
extra_rich_tags,
|
||||
extract_placeholders,
|
||||
load_jsonl,
|
||||
missing_rich_tags,
|
||||
parse_catalog,
|
||||
placeholder_counts_match,
|
||||
placeholders_match,
|
||||
unbalanced_rich_tags,
|
||||
validate_translation,
|
||||
)
|
||||
|
||||
@@ -109,11 +113,28 @@ def main() -> int:
|
||||
if args.placeholders_only and not problem.startswith("placeholder mismatch"):
|
||||
continue
|
||||
errors.append(f"{label}: {problem}")
|
||||
if placeholders_match(message.identity.source, value) and not placeholder_counts_match(
|
||||
message.identity.source, value
|
||||
):
|
||||
source_placeholders = extract_placeholders(message.identity.source)
|
||||
translated_placeholders = extract_placeholders(value)
|
||||
warnings.append(
|
||||
f"{label}: placeholder count mismatch: source={dict(source_placeholders)} "
|
||||
f"translation={dict(translated_placeholders)}"
|
||||
)
|
||||
if not args.placeholders_only:
|
||||
extras = extra_rich_tags(message.identity.source, value)
|
||||
if extras:
|
||||
output = errors if args.strict_extra_tags else warnings
|
||||
output.append(f"{label}: additional rich-text tags: {dict(extras)}")
|
||||
extras = missing_rich_tags(message.identity.source, value)
|
||||
if extras:
|
||||
output = errors if args.strict_extra_tags else warnings
|
||||
output.append(f"{label}: missing rich-text tags: {dict(extras)}")
|
||||
unbalanced = unbalanced_rich_tags(value)
|
||||
if unbalanced:
|
||||
output = errors if args.strict_extra_tags else warnings
|
||||
output.append(f"{label}: unbalanced rich-text tags (opening, closing): {unbalanced}")
|
||||
if (
|
||||
message.identity.numerus
|
||||
and any(value.strip() for value in values_for(message))
|
||||
|
||||
@@ -728,7 +728,7 @@ bool AutoUpdaterDialog::extractUpdater(const std::string& zip_path, const std::s
|
||||
tr("<h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div "
|
||||
"align=\"center\"><pre>%1</pre></div><p><strong>This is usually a result of manually renaming the "
|
||||
"file.</strong> Continuing to install this update may result in a broken installation if the renamed "
|
||||
"executable is used. The DuckStation executable should be named:<div "
|
||||
"executable is used. The DuckStation executable should be named:</p><div "
|
||||
"align=\"center\"><pre>%2</pre></div><p>Do you want to continue anyway?</p>")
|
||||
.arg(QString::fromStdString(std::string(check_for_file)))
|
||||
.arg(QStringLiteral(UPDATER_EXPECTED_EXECUTABLE)),
|
||||
|
||||
@@ -149,7 +149,7 @@ CaptureSettingsWidget::CaptureSettingsWidget(SettingsWindow* dialog, QWidget* pa
|
||||
"file will only contain audio."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.videoCaptureCodec, tr("Video Codec"), tr("Default"),
|
||||
tr("Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.<b>"));
|
||||
tr("Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.</b>"));
|
||||
dialog->registerWidgetHelp(m_ui.videoCaptureBitrate, tr("Video Bitrate"),
|
||||
tr("%1 kbps").arg(Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_BITRATE),
|
||||
tr("Sets the video bitrate to be used. Larger bitrate generally yields better video "
|
||||
@@ -170,7 +170,7 @@ CaptureSettingsWidget::CaptureSettingsWidget(SettingsWindow* dialog, QWidget* pa
|
||||
"file will only contain video."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.audioCaptureCodec, tr("Audio Codec"), tr("Default"),
|
||||
tr("Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.<b>"));
|
||||
tr("Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.</b>"));
|
||||
dialog->registerWidgetHelp(m_ui.audioCaptureBitrate, tr("Audio Bitrate"),
|
||||
tr("%1 kbps").arg(Settings::DEFAULT_MEDIA_CAPTURE_AUDIO_BITRATE),
|
||||
tr("Sets the audio bitrate to be used."));
|
||||
|
||||
@@ -6667,8 +6667,8 @@ Haz clic en Reiniciar para restablecer el número de serie al que se ha encontra
|
||||
<source>Rewind for %n frame(s), lasting %1 second(s) will require %2MB of RAM.</source>
|
||||
<translatorcomment>Plural here is for "frames" only, not "seconds".</translatorcomment>
|
||||
<translation>
|
||||
<numerusform>%n fotograma y %1 segundos de rebobinado requerirán %2 MB de RAM y %3 MB de VRAM.</numerusform>
|
||||
<numerusform>%n fotogramas y %1 segundos de rebobinado requerirán %2 MB de RAM y %3 MB de VRAM.</numerusform>
|
||||
<numerusform>%n fotograma y %1 segundos de rebobinado requerirán %2 MB de RAM.</numerusform>
|
||||
<numerusform>%n fotogramas y %1 segundos de rebobinado requerirán %2 MB de RAM.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -9820,7 +9820,7 @@ Esta acción no puede deshacerse.</translation>
|
||||
Error: {}
|
||||
Please check your username and password, and try again.</source>
|
||||
<translation>No se ha podido iniciar la sesión.
|
||||
Error: %1
|
||||
Error: {}
|
||||
Comprueba tu nombre de usuario y contraseña y vuelve a intentarlo.</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -11273,7 +11273,7 @@ Comprueba tu nombre de usuario y contraseña y vuelve a intentarlo.</translation
|
||||
<message>
|
||||
<location filename="../../core/fullscreenui_strings.h" line="908"/>
|
||||
<source>Yes, {} now and risk memory card corruption.</source>
|
||||
<translation>Sí, correr el riesgo de corromper la Memory Card.</translation>
|
||||
<translation>Sí, {} ahora y correr el riesgo de corromper la Memory Card.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/fullscreenui_strings.h" line="909"/>
|
||||
@@ -15968,7 +15968,7 @@ Clic+Mayús: establecer varias asignaciones.</translation>
|
||||
<location filename="../logwindow.cpp" line="64"/>
|
||||
<source>Dropped %1 log messages, please use file or system console logging.
|
||||
</source>
|
||||
<translation>Se han omitido %d mensajes del registro, comprueba el registro por archivo o consola del sistema.</translation>
|
||||
<translation>Se han omitido %1 mensajes del registro, comprueba el registro por archivo o consola del sistema.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -4371,7 +4371,7 @@ Non puoi annullare quest'azione.</translation>
|
||||
<message>
|
||||
<location filename="../coverdownloaddialog.ui" line="50"/>
|
||||
<source><html><head/><body><p>In the box below, specify the URLs to download covers from, with one template URL per line. The following variables are available:</p><p><span style=" font-style:italic;">${title}:</span> Title of the game.<br/><span style=" font-style:italic;">${filetitle}:</span> Name component of the game's filename.<br/><span style=" font-style:italic;">${serial}:</span> Serial of the game.</p><p><span style=" font-weight:700;">Example:</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html></source>
|
||||
<translation><html><head/><body><p>Nella casella qui sotto, specifica gli URL da cui scaricare le copertine, con un modello URL per riga. Le seguenti variabili sono disponibili:</p><p><span style=" font-style:italic;">${title}:</span> Titolo del gioco.<br/><span style=" font-style:italic;">${filetitle}:</span> Componente nome del nome file del gioco.<br/><span style=" font-style:italic;">${serial}:</span> Seriale del gioco.</p><p><span style=" font-weight:700;">Esempio:</span> https://www.esempio-non-un-dominio-reale.com/copertine/${seriale}.jpg</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Nella casella qui sotto, specifica gli URL da cui scaricare le copertine, con un modello URL per riga. Le seguenti variabili sono disponibili:</p><p><span style=" font-style:italic;">${title}:</span> Titolo del gioco.<br/><span style=" font-style:italic;">${filetitle}:</span> Componente nome del nome file del gioco.<br/><span style=" font-style:italic;">${serial}:</span> Seriale del gioco.</p><p><span style=" font-weight:700;">Esempio:</span> https://www.esempio-non-un-dominio-reale.com/copertine/${serial}.jpg</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../coverdownloaddialog.ui" line="63"/>
|
||||
|
||||
@@ -2550,8 +2550,8 @@ You can manually update DuckStation by re-downloading the latest release. Do you
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../autoupdaterdialog.cpp" line="728"/>
|
||||
<source><h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div align="center"><pre>%1</pre></div><p><strong>This is usually a result of manually renaming the file.</strong> Continuing to install this update may result in a broken installation if the renamed executable is used. The DuckStation executable should be named:<div align="center"><pre>%2</pre></div><p>Do you want to continue anyway?</p></source>
|
||||
<translation><h1>アプリケーションの状態が一致しません</h1><h3>更新用 ZIP に現在の実行ファイルが含まれていません:</h3><div align="center"><pre>%1</pre></div><p><strong>通常、これはファイル名を手動で変更したことが原因です。</strong>名前を変更した実行ファイルを使用している場合、この更新を続行するとインストールが破損する可能性があります。DuckStation の実行ファイル名は次のとおりである必要があります:<div align="center"><pre>%2</pre></div><p>それでも続行しますか?</p></translation>
|
||||
<source><h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div align="center"><pre>%1</pre></div><p><strong>This is usually a result of manually renaming the file.</strong> Continuing to install this update may result in a broken installation if the renamed executable is used. The DuckStation executable should be named:</p><div align="center"><pre>%2</pre></div><p>Do you want to continue anyway?</p></source>
|
||||
<translation><h1>アプリケーションの状態が一致しません</h1><h3>更新用 ZIP に現在の実行ファイルが含まれていません:</h3><div align="center"><pre>%1</pre></div><p><strong>通常、これはファイル名を手動で変更したことが原因です。</strong>名前を変更した実行ファイルを使用している場合、この更新を続行するとインストールが破損する可能性があります。DuckStation の実行ファイル名は次のとおりである必要があります:</p><div align="center"><pre>%2</pre></div><p>それでも続行しますか?</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../autoupdaterdialog.cpp" line="832"/>
|
||||
@@ -3086,8 +3086,8 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="152"/>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<translation>メディアキャプチャに使用する映像コーデックを選択します。<b>不明な場合は、デフォルトのままにしてください。<b></translation>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>メディアキャプチャに使用する映像コーデックを選択します。<b>不明な場合は、デフォルトのままにしてください。</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="153"/>
|
||||
@@ -3160,8 +3160,8 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="173"/>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<translation>メディアキャプチャに使用する音声コーデックを選択します。<b>不明な場合は、デフォルトのままにしてください。<b></translation>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>メディアキャプチャに使用する音声コーデックを選択します。<b>不明な場合は、デフォルトのままにしてください。</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="174"/>
|
||||
|
||||
@@ -6954,7 +6954,7 @@ You cannot undo this action.</source>
|
||||
<message>
|
||||
<location filename="../../core/fullscreen_ui.cpp" line="9516"/>
|
||||
<source>BIOS to use when emulating {} consoles.</source>
|
||||
<translation>콘솔을 에뮬레이트할 때 사용할 바이오스입니다.</translation>
|
||||
<translation>{} 콘솔을 에뮬레이트할 때 사용할 바이오스입니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/fullscreen_ui.cpp" line="9517"/>
|
||||
|
||||
@@ -9806,7 +9806,7 @@ Esta ação não pode ser desfeita.</translation>
|
||||
<message>
|
||||
<location filename="../../core/fullscreenui_strings.h" line="337"/>
|
||||
<source>Example: https://www.example-not-a-real-domain.com/covers/${serial}.jpg</source>
|
||||
<translation>Exemplo: https://www.exemplo-nao-eh-um-site.com/capas/${numeroserial}.jpg</translation>
|
||||
<translation>Exemplo: https://www.exemplo-nao-eh-um-site.com/capas/${serial}.jpg</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/fullscreenui_strings.h" line="340"/>
|
||||
|
||||
@@ -4233,7 +4233,7 @@ Esta ação não pode ser anulada.</translation>
|
||||
<message>
|
||||
<location filename="../../core/cpu_core.cpp" line="2074"/>
|
||||
<source>No return instruction found after %u instructions for step-out at %08X.</source>
|
||||
<translation>Nenhuma instrução de regresso encontrada após % u instruções para saída em %08X.</translation>
|
||||
<translation>Nenhuma instrução de regresso encontrada após %u instruções para saída em %08X.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -12238,7 +12238,7 @@ Tem a certeza que pretende continuar?</translation>
|
||||
<message>
|
||||
<location filename="../../core/memory_card.cpp" line="288"/>
|
||||
<source>Memory card at '%s' could not be read, formatting.</source>
|
||||
<translation>O cartão de memória em '% s' não pode ser lido, a formatar.</translation>
|
||||
<translation>O cartão de memória em '%s' não pode ser lido, a formatar.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/memory_card.cpp" line="333"/>
|
||||
|
||||
@@ -440,7 +440,7 @@ Tek kullanımlık giriş kodu (access token) oluşturulma tarihi: %2.</translati
|
||||
<message>
|
||||
<location filename="../../core/achievements.cpp" line="1679"/>
|
||||
<source>Your Value: {}</source>
|
||||
<translation>Puanın: {}}</translation>
|
||||
<translation>Puanın: {}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/achievements.cpp" line="1688"/>
|
||||
@@ -4894,7 +4894,7 @@ Tüm atamalar ve ayarlar silinecek bunu geri alamazsınız.</translation>
|
||||
<message>
|
||||
<location filename="../coverdownloaddialog.ui" line="50"/>
|
||||
<source><html><head/><body><p>In the box below, specify the URLs to download covers from, with one template URL per line. The following variables are available:</p><p><span style=" font-style:italic;">${title}:</span> Title of the game.<br/><span style=" font-style:italic;">${filetitle}:</span> Name component of the game's filename.<br/><span style=" font-style:italic;">${serial}:</span> Serial of the game.</p><p><span style=" font-weight:700;">Example:</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html></source>
|
||||
<translation><html><head/><body><p>Aşağıdaki kutuda kapak görseli indirilecek sitelerin linklerini yazabilirsiniz, Her satır için bir satır şablon link olacak şekilde. Şu değişkenler ayarlanabilir:</p><p><span style=" font-style:italic;">${İsim}:</span> Oyunun ismi<br/><span style=" font-style:italic;">${Dosya İsmi}:</span> Oyunun dosya ismi.<br/><span style=" font-style:italic;">${seri no}:</span> Oyunun seri numarası.</p><p><span style=" font-weight:700;">Örnek:</span> https://www.ornek/kapaklar/${seri-no}.jpg</p></body></html></translation>
|
||||
<translation><html><head/><body><p>Aşağıdaki kutuda kapak görseli indirilecek sitelerin linklerini yazabilirsiniz, Her satır için bir satır şablon link olacak şekilde. Şu değişkenler ayarlanabilir:</p><p><span style=" font-style:italic;">${title}:</span> Oyunun ismi<br/><span style=" font-style:italic;">${filetitle}:</span> Oyunun dosya ismi.<br/><span style=" font-style:italic;">${serial}:</span> Oyunun seri numarası.</p><p><span style=" font-weight:700;">Örnek:</span> https://www.ornek/kapaklar/${serial}.jpg</p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../coverdownloaddialog.ui" line="69"/>
|
||||
@@ -10485,12 +10485,12 @@ Yinede işleminize {0} devam etmek istiyor musunuz?</translation>
|
||||
<message>
|
||||
<location filename="../../core/fullscreen_ui.cpp" line="9831"/>
|
||||
<source>{:%H:%M}</source>
|
||||
<translation>{:%Saat:%Dakika}</translation>
|
||||
<translation>{:%H:%M}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/fullscreen_ui.cpp" line="9832"/>
|
||||
<source>{:%Y-%m-%d %H:%M:%S}</source>
|
||||
<translation>{:%Yıl-%Ay-%Day %Saat:%Dakika:%Saniye}</translation>
|
||||
<translation>{:%Y-%m-%d %H:%M:%S}</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../core/fullscreen_ui.cpp" line="9833"/>
|
||||
|
||||
@@ -3374,7 +3374,7 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="151"/>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>Вибирає відеокодек для запису медіа. <b>Якщо не впевнені, залиште значення за замовчуванням.</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -3447,7 +3447,7 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="171"/>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>Вибирає аудіокодек для запису медіа. <b>Якщо не впевнені, залиште значення за замовчуванням.</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -2519,8 +2519,8 @@ You can manually update DuckStation by re-downloading the latest release. Do you
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../autoupdaterdialog.cpp" line="728"/>
|
||||
<source><h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div align="center"><pre>%1</pre></div><p><strong>This is usually a result of manually renaming the file.</strong> Continuing to install this update may result in a broken installation if the renamed executable is used. The DuckStation executable should be named:<div align="center"><pre>%2</pre></div><p>Do you want to continue anyway?</p></source>
|
||||
<translation><h1>应用程序状态不一致</h1><h3>更新压缩包缺少当前可执行文件:</h3><div align="center"><pre>%1</pre></div><p><strong>这通常是手动重命名文件造成的。</strong>如果继续安装此更新,使用重命名后的可执行文件可能会导致安装损坏。DuckStation可执行文件应命名为:<div align="center"><pre>%2</pre></div><p>您仍然想要继续吗?</p></translation>
|
||||
<source><h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div align="center"><pre>%1</pre></div><p><strong>This is usually a result of manually renaming the file.</strong> Continuing to install this update may result in a broken installation if the renamed executable is used. The DuckStation executable should be named:</p><div align="center"><pre>%2</pre></div><p>Do you want to continue anyway?</p></source>
|
||||
<translation><h1>应用程序状态不一致</h1><h3>更新压缩包缺少当前可执行文件:</h3><div align="center"><pre>%1</pre></div><p><strong>这通常是手动重命名文件造成的。</strong>如果继续安装此更新,使用重命名后的可执行文件可能会导致安装损坏。DuckStation可执行文件应命名为:</p><div align="center"><pre>%2</pre></div><p>您仍然想要继续吗?</p></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../autoupdaterdialog.cpp" line="832"/>
|
||||
@@ -3086,8 +3086,8 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="152"/>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<translation>选择用于媒体捕获的视频编解码器。<b>如果不确定,请保持默认。<b></translation>
|
||||
<source>Selects which Video Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>选择用于媒体捕获的视频编解码器。<b>如果不确定,请保持默认。</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="153"/>
|
||||
@@ -3160,8 +3160,8 @@ Your dump may be corrupted, or the physical disc is scratched.</source>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="173"/>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.<b></source>
|
||||
<translation>选择用于媒体捕获的音频编解码器。<b>如果不确定,请保持默认。<b></translation>
|
||||
<source>Selects which Audio Codec to be used for media capture. <b>If unsure, leave it on default.</b></source>
|
||||
<translation>选择用于媒体捕获的音频编解码器。<b>如果不确定,请保持默认。</b></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../capturesettingswidget.cpp" line="174"/>
|
||||
|
||||
Reference in New Issue
Block a user