From afeab2718683bff80eb862a3db2308c2015ea73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 5 Sep 2026 16:15:37 -0600 Subject: [PATCH 1/3] Don't load RAIntegration from an install we can't write to RAIntegration keeps its cache and local achievement data next to the executable. If PPSSPP is installed somewhere that needs elevation to write - Program Files being the obvious case - that write fails and takes the emulator down as soon as a set or code notes are loaded, with no log to show for it since the log can't be written either. Check whether the exe directory is writable before handing the DLL to rcheevos, and if it isn't, say so and point at the portable .zip instead. Achievements themselves still work, so carry on to the normal login. Fixes #21260 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup --- Common/File/FileUtil.cpp | 12 ++++++++++++ Common/File/FileUtil.h | 4 ++++ Core/RetroAchievements.cpp | 16 ++++++++++++++++ assets/lang/en_US.ini | 1 + 4 files changed, 33 insertions(+) diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 62020ee59b..218669cd68 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -1068,6 +1068,18 @@ bool CreateEmptyFile(const Path &filename) { return true; } +bool IsDirectoryWritable(const Path &path) { + // There's no portable way to ask, so just try it and clean up after ourselves. + const Path probe = path / ".ppsspp_write_test"; + FILE *file = OpenCFile(probe, "wb"); + if (!file) { + return false; + } + fclose(file); + Delete(probe, true); + return true; +} + // Deletes an empty directory, returns true on success // WARNING: On Android with content URIs, it will delete recursively! bool DeleteDir(const Path &path) { diff --git a/Common/File/FileUtil.h b/Common/File/FileUtil.h index 054b6c9d33..9b5a0e12cf 100644 --- a/Common/File/FileUtil.h +++ b/Common/File/FileUtil.h @@ -123,6 +123,10 @@ bool MoveIfFast(const Path &srcFilename, const Path &destFilename); // creates an empty file filename, returns true on success bool CreateEmptyFile(const Path &filename); +// Can we actually create files in this directory? Checks by trying, since permission bits +// don't tell the whole story (Windows ACLs, read-only mounts, ...). +bool IsDirectoryWritable(const Path &path); + // Opens ini file (cheats, texture replacements etc.) // TODO: Belongs in System or something. bool OpenFileInEditor(const Path &fileName); diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index a85f2fc2bf..723252e21a 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -37,6 +37,7 @@ #include "Common/Crypto/md5.h" #include "Common/Log.h" #include "Common/File/Path.h" +#include "Common/File/FileUtil.h" #include "Common/Net/HTTPRequest.h" #include "Common/Net/HTTPClient.h" #include "Common/System/OSD.h" @@ -704,6 +705,21 @@ void InitializeRAIntegration(void *windowHandle) { ERROR_LOG(Log::Achievements, "RAIntegration is enabled, but no main window handle was found."); return; } + + // RAIntegration writes its cache and local achievement data next to the executable. If we + // can't write there - the usual case being an install under Program Files - it takes the + // emulator down with it as soon as it loads a set, so refuse to load it at all. See #21260. + const Path &exeDir = File::GetExeDirectory(); + if (!File::IsDirectoryWritable(exeDir)) { + auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); + ERROR_LOG(Log::Achievements, "Not loading RAIntegration, '%s' is not writable", exeDir.c_str()); + g_OSD.Show(OSDType::MESSAGE_ERROR, ac->T("RAIntegrationNotWritable", + "RAIntegration needs to write next to PPSSPP.exe, which this install doesn't allow. Use the portable .zip version instead."), "", g_RAImageID, 10.0f); + // Carry on without the toolkit - plain achievements still work. + TryLoginByToken(true); + return; + } + rc_client_begin_load_raintegration(g_rcClient, szFilePath, hWnd, "PPSSPP", PPSSPP_GIT_VERSION, &load_integration_callback, hWnd); return; } diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 5b8dd9a2f0..d0a33db0c3 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -67,6 +67,7 @@ Missable = Missable Notifications = Notifications Progression = Progression RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration needs to write next to PPSSPP.exe, which this install doesn't allow. Use the portable .zip version instead. Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org From 2b16e107b9f24e7452f5598eaecf7d093af2239f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 09:02:19 -0600 Subject: [PATCH 2/3] Translate "RAIntegrationNotWritable" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 36 languages, following each file's existing handling of the sibling RAIntegration string - most keep the name as-is, pl_PL uses "Integracja RA", tr_TR "RA Entegrasyonu", ja_JP "RAインテグレーション" - and its formality. The rest are left to fall back to English rather than guessed at. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup --- assets/lang/ar_AE.ini | 1 + assets/lang/bg_BG.ini | 1 + assets/lang/ca_ES.ini | 1 + assets/lang/cz_CZ.ini | 1 + assets/lang/da_DK.ini | 1 + assets/lang/de_DE.ini | 1 + assets/lang/es_ES.ini | 1 + assets/lang/es_LA.ini | 1 + assets/lang/fi_FI.ini | 1 + assets/lang/fr_FR.ini | 1 + assets/lang/gl_ES.ini | 1 + assets/lang/gr_EL.ini | 1 + assets/lang/he_IL.ini | 1 + assets/lang/hr_HR.ini | 1 + assets/lang/hu_HU.ini | 1 + assets/lang/id_ID.ini | 1 + assets/lang/it_IT.ini | 1 + assets/lang/ja_JP.ini | 1 + assets/lang/ko_KR.ini | 1 + assets/lang/lt-LT.ini | 1 + assets/lang/ms_MY.ini | 1 + assets/lang/nl_NL.ini | 1 + assets/lang/nn_NO.ini | 1 + assets/lang/no_NO.ini | 1 + assets/lang/pl_PL.ini | 1 + assets/lang/pt_BR.ini | 1 + assets/lang/pt_PT.ini | 1 + assets/lang/ro_RO.ini | 1 + assets/lang/ru_RU.ini | 1 + assets/lang/sv_SE.ini | 1 + assets/lang/th_TH.ini | 1 + assets/lang/tr_TR.ini | 1 + assets/lang/uk_UA.ini | 1 + assets/lang/vi_VN.ini | 1 + assets/lang/zh_CN.ini | 1 + assets/lang/zh_TW.ini | 1 + 36 files changed, 36 insertions(+) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 61dd0b0de9..d2a8d0d6e5 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -51,6 +51,7 @@ Missable = مفقود Notifications = الإشعارات Progression = التقدم RAIntegration is enabled, but %1 was not found. = RAIntegration مفعل، لكن لم يتم العثور على %1. # AI translated +RAIntegrationNotWritable = يحتاج RAIntegration إلى الكتابة بجوار PPSSPP.exe، وهذا التثبيت لا يسمح بذلك. استخدم النسخة المحمولة .zip بدلاً من ذلك. # AI translated Recently unlocked = الإنجازات التي تم فتحها مؤخرا Reconnected to RetroAchievements. = RetroAchievements يتم إعادة الإتصال في Register on www.retroachievements.org = www.retroachievements.org سجل في diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index 0f2f8802c3..b710f1d7ed 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -43,6 +43,7 @@ Missable = Пропуснато Notifications = Notifications Progression = Напредък RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration трябва да записва до PPSSPP.exe, което тази инсталация не позволява. Използвайте преносимата .zip версия. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 4dfd773cda..31b43bb862 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -43,6 +43,7 @@ Missable = Perdible Notifications = Notifications Progression = Progressió RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration necessita escriure al costat de PPSSPP.exe, cosa que aquesta instal·lació no permet. Utilitza la versió portàtil en .zip. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index 497ee839a0..a811d646f5 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -43,6 +43,7 @@ Missable = Zmeškané Notifications = Notifications Progression = Progrese RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration potřebuje zapisovat vedle PPSSPP.exe, což tato instalace neumožňuje. Použijte místo toho přenosnou verzi .zip. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 5fe109bb54..a37e1c08e3 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -43,6 +43,7 @@ Missable = Mistet Notifications = Notifications Progression = Fremgang RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration skal kunne skrive ved siden af PPSSPP.exe, hvilket denne installation ikke tillader. Brug den portable .zip-version i stedet. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index aad2c9fb26..40cd5185c7 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -43,6 +43,7 @@ Missable = Verpassbar Notifications = Benachrichtigungen Progression = Fortschritt RAIntegration is enabled, but %1 was not found. = RAIntegration ist aktiviert, aber %1 wurde nicht gefunden +RAIntegrationNotWritable = RAIntegration muss neben PPSSPP.exe schreiben können, was diese Installation nicht erlaubt. Nutze stattdessen die portable .zip-Version. # AI translated Recently unlocked = Kürzlich freigeschaltete Erfolge Reconnected to RetroAchievements. = Wieder mit RetroAchievements verbunden Register on www.retroachievements.org = Registriere dich auf www.retroachievements.org diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index 45c9cada39..00f8632d95 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -43,6 +43,7 @@ Missable = Perdible Notifications = Notificaciones Progression = Progresión RAIntegration is enabled, but %1 was not found. = RAIntegration está activado, pero no se encontró %1. +RAIntegrationNotWritable = RAIntegration necesita escribir junto a PPSSPP.exe, algo que esta instalación no permite. Usa la versión portátil en .zip. # AI translated Recently unlocked = Desbloqueados recientemente Reconnected to RetroAchievements. = Reconectado a RetroAchievements. Register on www.retroachievements.org = Registrarse en www.retroachievements.org diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 2a274a9a77..f245402966 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -43,6 +43,7 @@ Missable = Perdible Notifications = Notificaciones Progression = Progresión RAIntegration is enabled, but %1 was not found. = RAIntegration habilitado, pero %1 no fue encontrado. +RAIntegrationNotWritable = RAIntegration necesita escribir junto a PPSSPP.exe, algo que esta instalación no permite. Usa la versión portátil en .zip. # AI translated Recently unlocked = Desbloqueados recientemente Reconnected to RetroAchievements. = Reconectado a RetroAchievements. Register on www.retroachievements.org = Registrate en www.retroachievements.org diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 3aa3aa8706..cde11dbad9 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -43,6 +43,7 @@ Missable = Hukkaava Notifications = Notifications Progression = Eteneminen RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration tarvitsee kirjoitusoikeuden PPSSPP.exe:n viereen, mitä tämä asennus ei salli. Käytä sen sijaan siirrettävää .zip-versiota. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Rekisteröidy osoitteessa www.retroachievements.org diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index db99844e67..ca9380751e 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -67,6 +67,7 @@ Missable = Manquable Notifications = Notifications Progression = Progression RAIntegration is enabled, but %1 was not found. = RAIntegration est activé, mais %1 est introuvable. +RAIntegrationNotWritable = RAIntegration doit pouvoir écrire à côté de PPSSPP.exe, ce que cette installation ne permet pas. Utilisez plutôt la version portable en .zip. # AI translated Recently unlocked = Débloqué récemment Reconnected to RetroAchievements. = Reconnexion à RetroAchievements. Register on www.retroachievements.org = Inscrivez-vous sur www.retroachievements.org diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index ea74131890..fde84262a5 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -43,6 +43,7 @@ Missable = Perdible Notifications = Notifications Progression = Progresión RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration necesita escribir xunto a PPSSPP.exe, algo que esta instalación non permite. Usa a versión portátil en .zip. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index 810236846d..d10b9ae819 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -43,6 +43,7 @@ Missable = Χαμένο Notifications = Notifications Progression = Πρόοδος RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = Το RAIntegration χρειάζεται να γράφει δίπλα στο PPSSPP.exe, κάτι που δεν επιτρέπει αυτή η εγκατάσταση. Χρησιμοποιήστε τη φορητή έκδοση .zip. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 5d7cd458aa..bc4eddf054 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -43,6 +43,7 @@ Missable = אבד Notifications = Notifications Progression = התקדמות RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration צריך לכתוב לצד PPSSPP.exe, וההתקנה הזו לא מאפשרת זאת. השתמש בגרסה הניידת (.zip) במקום. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index 9ead6e8346..54826a1235 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -43,6 +43,7 @@ Missable = Propušteno Notifications = Notifications Progression = Napredovanje RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration mora zapisivati pored PPSSPP.exe, što ova instalacija ne dopušta. Umjesto toga upotrijebite prijenosnu .zip verziju. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 062f8d3561..4980dcfc68 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -43,6 +43,7 @@ Missable = Elképzelhető Notifications = Értesítések Progression = Haladás RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = A RAIntegrationnek a PPSSPP.exe mellé kell írnia, amit ez a telepítés nem enged. Használd helyette a hordozható .zip verziót. # AI translated Recently unlocked = Legutóbb kioldott teljesítmények Reconnected to RetroAchievements. = Újracsatlakozva a RetroAchievements-hez Register on www.retroachievements.org = Regisztráció a www.retroachievements.org-on… diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 727074cb03..beac3e84e0 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -43,6 +43,7 @@ Missable = Tidak Terjangkau Notifications = Notifikasi Progression = Kemajuan RAIntegration is enabled, but %1 was not found. = RAIntegration diaktifkan, tetapi %1 tidak ditemukan. +RAIntegrationNotWritable = RAIntegration perlu menulis di samping PPSSPP.exe, tetapi instalasi ini tidak mengizinkannya. Gunakan versi portabel .zip sebagai gantinya. # AI translated Recently unlocked = Pencapaian yang baru saja dibuka Reconnected to RetroAchievements. = Terhubung kembali ke RetroAchievements. Register on www.retroachievements.org = Daftar di www.retroachievements.org diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index 19df73befe..8ac4ac54d8 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -43,6 +43,7 @@ Missable = Perdibile Notifications = Notifiche Progression = Progresso RAIntegration is enabled, but %1 was not found. = RAIntegration abilitata, ma %1 non trovato. +RAIntegrationNotWritable = RAIntegration deve poter scrivere accanto a PPSSPP.exe, cosa che questa installazione non consente. Usare invece la versione portatile in .zip. # AI translated Recently unlocked = Obiettivi recentemente sbloccati Reconnected to RetroAchievements. = Riconnesso a RetroAchievements. Register on www.retroachievements.org = Registrati su www.retroachievements.org diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 6822034cf4..68566fd69b 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -43,6 +43,7 @@ Missable = 逃す可能性 Notifications = 通知 Progression = 進行 RAIntegration is enabled, but %1 was not found. = RAインテグレーションは有効になっていますが、 %1 が見つかりませんでした。 +RAIntegrationNotWritable = RAインテグレーションはPPSSPP.exeと同じ場所に書き込む必要がありますが、このインストールでは許可されていません。代わりにポータブル版(.zip)を使用してください。 # AI translated Recently unlocked = 最近達成した実績 Reconnected to RetroAchievements. = RetroAchievementsに再接続しました。 Register on www.retroachievements.org = www.retroachievements.orgで登録 diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 8ae47ac3ff..4c0e6b3ac9 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -43,6 +43,7 @@ Missable = 놓칠 수 있는 Notifications = 알림 Progression = 진행 RAIntegration is enabled, but %1 was not found. = RAIntegration이 활성화되었지만, %1을 찾을 수 없습니다. +RAIntegrationNotWritable = RAIntegration은 PPSSPP.exe 옆에 파일을 써야 하지만, 이 설치에서는 허용되지 않습니다. 대신 포터블 .zip 버전을 사용하세요. # AI translated Recently unlocked = 최근 잠금 해제된 도전과제 Reconnected to RetroAchievements. = RetroAchievements에 다시 연결되었습니다. Register on www.retroachievements.org = www.retroachievements.org에 등록 diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 181f9f5342..9fed762e7f 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -43,6 +43,7 @@ Missable = Praleistas Notifications = Notifications Progression = Paskirtis RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration turi rašyti šalia PPSSPP.exe, o šis įdiegimas to neleidžia. Vietoje to naudokite nešiojamąją .zip versiją. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index fccb9e57c2..d8549eca7d 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -43,6 +43,7 @@ Missable = Boleh terlepas Notifications = Pemberitahuan Progression = Kemajuan RAIntegration is enabled, but %1 was not found. = RAIntegration diaktifkan, tetapi %1 tidak ditemui. +RAIntegrationNotWritable = RAIntegration perlu menulis di sebelah PPSSPP.exe, tetapi pemasangan ini tidak membenarkannya. Sebaliknya, gunakan versi mudah alih .zip. # AI translated Recently unlocked = Baru dibuka Reconnected to RetroAchievements. = Menyambung semula ke RetroAchievements. Register on www.retroachievements.org = Daftar di www.retroachievements.org diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index ad5269c9bd..627f99ef12 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -43,6 +43,7 @@ Missable = Misbaar Notifications = Notifications Progression = Voortgang RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration moet naast PPSSPP.exe kunnen schrijven, wat deze installatie niet toestaat. Gebruik in plaats daarvan de draagbare .zip-versie. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/nn_NO.ini b/assets/lang/nn_NO.ini index 039cfacbf1..89f992dd08 100644 --- a/assets/lang/nn_NO.ini +++ b/assets/lang/nn_NO.ini @@ -67,6 +67,7 @@ Missable = Kan gå glipp av Notifications = Varsel Progression = Framgang RAIntegration is enabled, but %1 was not found. = RAIntegration er aktivert, men %1 vart ikkje funnen. +RAIntegrationNotWritable = RAIntegration må kunna skriva ved sida av PPSSPP.exe, noko denne installasjonen ikkje tillèt. Bruk den portable .zip-versjonen i staden. # AI translated Recently unlocked = Nyleg låst opp Reconnected to RetroAchievements. = Kopla til RetroAchievements igjen. Register on www.retroachievements.org = Registrer deg på www.retroachievements.org diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 6f992f5bc2..3d9365aa96 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -67,6 +67,7 @@ Missable = Kan gå glipp av Notifications = Varsler Progression = Fremgang RAIntegration is enabled, but %1 was not found. = RAIntegration er aktivert, men %1 ble ikke funnet. +RAIntegrationNotWritable = RAIntegration må kunne skrive ved siden av PPSSPP.exe, noe denne installasjonen ikke tillater. Bruk den portable .zip-versjonen i stedet. # AI translated Recently unlocked = Nylig låst opp Reconnected to RetroAchievements. = Koblet til RetroAchievements igjen. Register on www.retroachievements.org = Registrer deg på www.retroachievements.org diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 1ac3932d8d..f73761e41b 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -43,6 +43,7 @@ Missable = Zaginiony Notifications = Powiadomiemia Progression = Postęp RAIntegration is enabled, but %1 was not found. = Integracja RA jest włączona, ale nie udało się znaleźć %1. +RAIntegrationNotWritable = Integracja RA musi zapisywać dane obok PPSSPP.exe, na co ta instalacja nie pozwala. Użyj przenośnej wersji .zip. # AI translated Recently unlocked = Ostatnio odblokowane osiągnięcia Reconnected to RetroAchievements. = Połączono ponownie z RetroAchievements. Register on www.retroachievements.org = Zarejestruj się na www.retroachievements.org diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 9f3c75a40c..48b3f0f701 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -67,6 +67,7 @@ Missable = Perdível Notifications = Notificações Progression = Progresso RAIntegration is enabled, but %1 was not found. = O RAIntegration está ativado mas o %1 não foi achado. +RAIntegrationNotWritable = O RAIntegration precisa gravar ao lado do PPSSPP.exe, o que esta instalação não permite. Use a versão portátil em .zip. # AI translated Recently unlocked = Destrancadas recentemente Reconnected to RetroAchievements. = Reconectou com o RetroAchievements. Register on www.retroachievements.org = Registrar no www.retroachievements.org diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 0f37c1ed20..bf5f998752 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -67,6 +67,7 @@ Missable = Perdível Notifications = Notificações Progression = Progresso RAIntegration is enabled, but %1 was not found. = A RAIntegration está ativada, mas %1 não foi encontrado(a). +RAIntegrationNotWritable = A RAIntegration precisa de escrever junto ao PPSSPP.exe, o que esta instalação não permite. Use a versão portátil em .zip. # AI translated Recently unlocked = Conquistas recentemente desbloqueadas Reconnected to RetroAchievements. = Reconectado à RetroAchievements. Register on www.retroachievements.org = Cadastra-te em www.retroachievements.org diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 9798c4888b..cd1375dfeb 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -43,6 +43,7 @@ Missable = Pierdut Notifications = Notifications Progression = Progres RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration trebuie să scrie lângă PPSSPP.exe, lucru pe care această instalare nu îl permite. Folosește versiunea portabilă .zip. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index e34f866df4..349a920665 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -43,6 +43,7 @@ Missable = Могут быть пропущены Notifications = Уведомления Progression = Прогресс RAIntegration is enabled, but %1 was not found. = RAIntegration включён, но %1 не найден. +RAIntegrationNotWritable = RAIntegration требуется запись рядом с PPSSPP.exe, но эта установка этого не позволяет. Используйте портативную версию из .zip. # AI translated Recently unlocked = Недавно разблокированные Reconnected to RetroAchievements. = Повторно подключено к RetroAchievements. Register on www.retroachievements.org = Зарегистрироваться на www.retroachievements.org diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 96dc1f7d10..6dc489236a 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -43,6 +43,7 @@ Missable = Kan missas Notifications = Aviseringar Progression = Framsteg RAIntegration is enabled, but %1 was not found. = RAIntegration är aktiverat men %1 hittades inte. +RAIntegrationNotWritable = RAIntegration behöver kunna skriva bredvid PPSSPP.exe, vilket den här installationen inte tillåter. Använd den portabla .zip-versionen istället. # AI translated Recently unlocked = Nyligen avklarade prestationer Reconnected to RetroAchievements. = Återansluten till RetroAchievements. Register on www.retroachievements.org = Registrera dig på www.retroachievements.org diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index d03d1e96b4..6924b34769 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -43,6 +43,7 @@ Missable = พลาด Notifications = การแจ้งเตือน Progression = ความก้าวหน้า RAIntegration is enabled, but %1 was not found. = RAIntegration ถูกเปิดใช้งานแล้ว แต่ไม่พบ %1 +RAIntegrationNotWritable = RAIntegration ต้องเขียนไฟล์ข้าง PPSSPP.exe แต่การติดตั้งนี้ไม่อนุญาต กรุณาใช้เวอร์ชันพกพา .zip แทน # AI translated Recently unlocked = เป้าหมายความสำเร็จที่ถูกปลดล็อคล่าสุด Reconnected to RetroAchievements. = กำลังเชื่อมต่ออีกครั้งไปยัง RetroAchievements Register on www.retroachievements.org = สมัครได้ที่ www.retroachievements.org diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index f51c02e979..bee33a0218 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -43,6 +43,7 @@ Missable = Atlanabilir Notifications = Bildirimler Progression = İlerleme RAIntegration is enabled, but %1 was not found. = RA Entegrasyonu etkinleştirildi, %1 bulunamadı. +RAIntegrationNotWritable = RA Entegrasyonu'nun PPSSPP.exe yanına yazması gerekiyor, bu kurulum ise buna izin vermiyor. Bunun yerine taşınabilir .zip sürümünü kullanın. # AI translated Recently unlocked = Yakın zamanda kilidi açılan başarımlar Reconnected to RetroAchievements. = RetroAchievements yeniden bağlanıldı. Register on www.retroachievements.org = Kayıt Ol www.retroachievements.org diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 691a01f79f..22dc8264e9 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -43,6 +43,7 @@ Missable = Пропущене Notifications = Сповіщення Progression = Прогрес RAIntegration is enabled, but %1 was not found. = RAIntegration включено, але %1 не знайдено. +RAIntegrationNotWritable = RAIntegration потребує запису поряд із PPSSPP.exe, але ця інсталяція цього не дозволяє. Скористайтеся портативною версією із .zip. # AI translated Recently unlocked = Нещодавно розблоковані відзнаки Reconnected to RetroAchievements. = Повторне підключення до РетроВідзнаки. Register on www.retroachievements.org = Зареєструватися на www.retroachievements.org diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 805cf28cc5..7b7920fc72 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -43,6 +43,7 @@ Missable = Có thể bỏ lỡ Notifications = Notifications Progression = Tiến trình RAIntegration is enabled, but %1 was not found. = RAIntegration is enabled, but %1 was not found. +RAIntegrationNotWritable = RAIntegration cần ghi dữ liệu cạnh PPSSPP.exe, nhưng bản cài đặt này không cho phép. Hãy dùng bản portable .zip thay thế. # AI translated Recently unlocked = Recently unlocked Reconnected to RetroAchievements. = Reconnected to RetroAchievements. Register on www.retroachievements.org = Register on www.retroachievements.org diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index 9f519b70f1..88aa01acd3 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -43,6 +43,7 @@ Missable = 错过的 Notifications = 通知 Progression = 进展 RAIntegration is enabled, but %1 was not found. = 已启用 RAIntegration,但 %1 未找到。 +RAIntegrationNotWritable = RAIntegration 需要在 PPSSPP.exe 旁边写入文件,但此安装方式不允许。请改用便携版 .zip。 # AI translated Recently unlocked = 最近解锁的成就 Reconnected to RetroAchievements. = 重新连接RetroAchievements。 Register on www.retroachievements.org = 在www.retroachievements.org注册 diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 8a5c6c3ef0..f709d2c3b8 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -43,6 +43,7 @@ Missable = 遺失的 Notifications = 通知 Progression = 進展 RAIntegration is enabled, but %1 was not found. = 已啟用 RAIntegration,但未找到 %1。 +RAIntegrationNotWritable = RAIntegration 需要在 PPSSPP.exe 旁邊寫入檔案,但此安裝方式不允許。請改用可攜版 .zip。 # AI translated Recently unlocked = 最近解鎖的成就 Reconnected to RetroAchievements. = 已重新連線至 RetroAchievements。 Register on www.retroachievements.org = 在 www.retroachievements.org 上註冊 From 3af608007571af5bc0ba19bc26759bf50846dd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 09:02:37 -0600 Subject: [PATCH 3/3] add-string: check the arguments before translating anything The command splits $1/$2/$3 off the invocation positionally and then states them as fact, so calling it with a sentence rather than `/add-string
""` yields three arbitrary words - and nothing downstream notices. Ask for them to be checked against en_US.ini first, and to re-derive the real section and key from the request if they don't hold up. Also spell out, in both the command and docs/translations.md, that the en_US line in the scratch file overwrites en_US.ini like any other language, so for an existing key it has to match the current English text exactly - otherwise it quietly rewords the string every other language was translated from. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GgACRqkQNpfJQ4fjwyoEup --- .claude/commands/add-string.md | 15 +++++++++++++-- docs/translations.md | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.claude/commands/add-string.md b/.claude/commands/add-string.md index a091d1365b..17860191d3 100644 --- a/.claude/commands/add-string.md +++ b/.claude/commands/add-string.md @@ -10,6 +10,14 @@ Add and/or translate a PPSSPP UI string. - English string: `$3` - if this is empty, the key already exists in `assets/lang/en_US.ini` and you're only filling in the languages where it's still untranslated. +Those three are split off the invocation positionally, so they're only right if it was called as +`/add-string
"" [""]`. Called with a sentence instead, they'll be +three arbitrary words - so **check them before you touch anything**: `$1` has to be a real `[Section]` +in `assets/lang/en_US.ini`, and `$2` a key that exists under it (or, for a new string, one that +doesn't exist anywhere yet and that you can find in the C++). If they don't hold up, work out the +real section and key from what was actually asked for, say which values you settled on, and carry on +from there - don't translate whatever the positional split happened to produce. + Follow the workflow in docs/translations.md. Run langtool from `Tools/langtool`: @@ -36,8 +44,11 @@ Follow the workflow in docs/translations.md. Run langtool from One line per language, named after the ini file minus the extension (`lt-LT`, `he_IL_invert`, `zh_TW`, ...), plus an `en_US` line carrying the English string itself if you were given one - - that's what creates the key in `en_US.ini`. No trailing `# comments` on those lines, they'd end - up inside the translation. Placeholders like `%1` and `%d` have to appear verbatim in the + that's what creates the key in `en_US.ini`. For a key that already exists, that line has to be + the existing English text character for character: it overwrites `en_US.ini` like any other + language, so a stray reword there silently changes the source string every other language was + translated from. Diff `en_US.ini` afterwards to confirm it didn't move. + No trailing `# comments` on those lines, they'd end up inside the translation. Placeholders like `%1` and `%d` have to appear verbatim in the translation, in whatever position the target language needs them. **If you don't know a language well enough to be confident, leave it out.** A key that's missing diff --git a/docs/translations.md b/docs/translations.md index 1c4caa8aca..dfda39a546 100644 --- a/docs/translations.md +++ b/docs/translations.md @@ -21,6 +21,10 @@ from `Tools/langtool`: sv_SE = Teststräng lt-LT = Testeilutė ``` + For a key that already exists, the `en_US` line has to be the current English text character for + character - it overwrites `en_US.ini` like any other language, so a stray reword there quietly + changes the string all the others were just translated from. Diff `en_US.ini` afterwards to check + it didn't move. No trailing `# comments` on those lines, they'd end up inside the translation. Placeholders have to survive verbatim. If you don't know a language well enough, leave it out - a key that's missing from a language file falls back to the English string at runtime, which is much better than a confident