Skip to content

add vandalism detection script - #48

Closed
f321x wants to merge 2 commits into
spesmilo:masterfrom
f321x:vandalism_detection
Closed

add vandalism detection script#48
f321x wants to merge 2 commits into
spesmilo:masterfrom
f321x:vandalism_detection

Conversation

@f321x

@f321x f321x commented Feb 17, 2026

Copy link
Copy Markdown
Member

Adds a script utilizing a LLM to check .po translation files for vandalized translations.

I think it is reasonably reliable and seems to tend more on the false positive side.

Using google/gemini-3-flash-preview it costs roughly ~0.02 usd cent per translation (on ppq.ai).
We have ~184660 translations, so running this script once through the whole repo would
cost ~36$ using gemini-3-flash-preview.
This should hopefully allow us to detect most of the vandalism that has happened over time.

The script itself and the unittests were mostly written by an LLM as well, i reviewed (and tested) them and they seem to do the job.

For the future we could modify the script to run it in the CI and check only the diff of new translations if it turns out to be accurate enough.

Edit:

We have 184660 translations, so running this script once through the whole repo would cost 36$ using gemini-3-flash-preview.

The sum of ~184660 translations i used for the calculation above is the sum of all words from the crowdin UI, though what actually matters is the count of strings, which is much lower ~42k, resulting in a lower cost of only roughly ~9$ to scan the whole translation set.

@f321x
f321x force-pushed the vandalism_detection branch 5 times, most recently from ef1097b to cb8e0b3 Compare February 19, 2026 08:53
Adds a script utilizing a LLM to check .po translation files for
vandalized translations.
@f321x
f321x force-pushed the vandalism_detection branch from cb8e0b3 to f13ef8a Compare February 19, 2026 09:19
the *summary.txt file contains all results but might not align 100% with
the json files as I stitched the result of 2 partial runs together
(they had some overlap so i combined the positive results).
@f321x

f321x commented Feb 19, 2026

Copy link
Copy Markdown
Member Author

Summary of the 19.02.2026 run:

Click to expand

Total spam entries detected: 164

────────────────────────────────────────
LOCALE: ar_SA (5 entries)
────────────────────────────────────────

Original: - an arbitrary on-chain script, e.g.:
Translation: مشغول

Original: Add to coin control
Translation: Add to coin control

Original: Cannot start QR scanner, no usable camera found.
Translation: غير مسموح لك في هذا الجهاز باستخدام كير

Original: Cannot start QR scanner: initialization failed.
Translation: غير مسموح لك بالقيام بكير تلف في البيانات

Original: Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system.
Translation: Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system.

────────────────────────────────────────
LOCALE: az_AZ (2 entries)
────────────────────────────────────────

Original: Details
Translation: Detallar

Original: Close Electrum?
Translation: Electrum bağlanılsın?

────────────────────────────────────────
LOCALE: bg_BG (1 entries)
────────────────────────────────────────

Original: unknown
Translation: неизвестен

────────────────────────────────────────
LOCALE: cs_CZ (1 entries)
────────────────────────────────────────

Original: &Pay to many
Translation: &Hromadná platba

────────────────────────────────────────
LOCALE: da_DK (3 entries)
────────────────────────────────────────

Original: Save
Translation: Gem

Original: Save
Translation: Gem

Original: Save
Translation: Gem

────────────────────────────────────────
LOCALE: de_DE (2 entries)
────────────────────────────────────────

Original: Terms of Use
Translation: Nutzungsbedingungen

Original: Server settings
Translation: Servereinstellungen

────────────────────────────────────────
LOCALE: el_GR (1 entries)
────────────────────────────────────────

Original: Exit Electrum
Translation: Έξοδος από το Electrum

────────────────────────────────────────
LOCALE: eo_UY (1 entries)
────────────────────────────────────────

Original: Close
Translation: Fermi

────────────────────────────────────────
LOCALE: es_ES (18 entries)
────────────────────────────────────────

Original: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.
Translation: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction

Original: Cannot determine dynamic fees, not connected
Translation: Key: Cannot determine dynamic fees, not connected\n#: electrum/gui/qml/qetxfinalizer.py:431\nFile: messages.pot

Original: Cannot pay less than the amount specified in the invoice
Translation: Cannot pay less than the amount specified in the invoice

Original: Error parsing Lightning invoice
Translation: Error al analizar la factura Lightning\n SOLICITUD DE CONTEXTO

Original: Fees are paid by the sender.
Translation: Las tarifas las paga el remitente.\n SOLICITUD DE CONTEXTO

Original: Integers weights can also be used in conjunction with '!', e.g. set one amount to '2!' and another to '3!' to split your coins 40-60.
Translation: Los pesos enteros también se pueden usar junto con '!', P. Ej. establezca una cantidad en '2!' y otro a '¡3!' para dividir sus monedas 40-60.\n SOLICITUD DE CONTEXTO

Original: Invoice has unknown status
Translation: Key: Invoice has unknown status\n#: electrum/gui/qml/qeinvoice.py:295 electrum/gui/qml/qeinvoice.py:312\nFile: messages.pot

Original: Raw
Translation: Proporciona soporte para la cartera de hardware Safe-T mini

Original: Rebalance your channels in order to increase your sending or receiving capacity
Translation: Rebalance your channels in order to increase your sending or receiving capacity

Original: The platform QR detection library is not available.
Translation: The platform QR detection library is not available.

Original: There are still channels that are not fully closed
Translation: There are still channels that are not fully closed

Original: There are still coins present in this wallet. Really delete?
Translation: There are still coins present in this wallet. Really delete?

Original: There are still unpaid requests. Really delete?
Translation: There are still unpaid requests. Really delete?

Original: This is a channel backup.
Translation: This is a channel backup.

Original: You can use it to request a force close.
Translation: You can use it to request a force close.

Original: You will be able to pay once the swap is confirmed.
Translation: You will be able to pay once the swap is confirmed.

Original: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.
Translation: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction

Original: This channel will be usable after %1 confirmations
Translation: Este canal sera inutilizable después de %1 confirmaciones

────────────────────────────────────────
LOCALE: fa_IR (1 entries)
────────────────────────────────────────

Original: (No FX rate available)
Translation: متن

────────────────────────────────────────
LOCALE: fr_FR (1 entries)
────────────────────────────────────────

Original: CLTV expiry
Translation: CLTV expiration

────────────────────────────────────────
LOCALE: id_ID (10 entries)
────────────────────────────────────────

Original: Add a cosigner to your multi-sig wallet
Translation: Tambahkan pengirim barang ke multi-sig wallet milik anda

Original: Address unknown for node:
Translation: Tambahkan alamat cadangan ke faktur kilat BOLT11.

Original: All outputs are non-change is_mine
Translation: Izinkan pertukaran instan

Original: Cannot create child transaction
Translation: Transaksi oleh anak kecil tidak bisa di proses.

Original: Offline
Translation: Luring

Original: Remote Node
Translation: Hapus Node

Original: Save
Translation: Benih

Original: Your wallet history has been successfully exported.
Translation: Twoja historia portfela została pomyślnie wyeksportowana.

Original: Save
Translation: Benih

Original: Save
Translation: Benih

────────────────────────────────────────
LOCALE: it_IT (3 entries)
────────────────────────────────────────

Original: Cannot have witness v1+ in p2sh
Translation: Impossibile determinare le tariffe dinamiche, non connesso

Original: [p] - print stored payment order
Translation: [p] - incolla ordine pagamento archiviato

Original: Transaction is unrelated to this wallet.
Translation: Transazione non legata a questo portafogli.

────────────────────────────────────────
LOCALE: ja_JP (6 entries)
────────────────────────────────────────

Original: Invalid Public key
Translation: 無効な秘密鍵です

Original: Not Now
Translation: Not Now

Original: Redeem Script
Translation: 文字列に変換

Original: Distributed by Electrum Technologies GmbH
Translation: توضیع شده توسط Electrum Technologies GmbH

Original: Not Now
Translation: Not Now

Original: Detect Existing Accounts
Translation: 既存のアカウントを削除

────────────────────────────────────────
LOCALE: ko_KR (8 entries)
────────────────────────────────────────

Original: Go online to complete wallet creation
Translation: Go online to complete wallet creation

Original: Never disclose your seed.
Translation: 당신의 씨앗을 공개하지 마십시오. 웹 사이트에 입력하지 마십시오.

Original: No donation address for this server
Translation: 이메일 주소 알림

Original: Request force-close
Translation: リクエストを強制クローズ

Original: Sign/verify Message
Translation: 로그인

Original: Success
Translation: 성ㄱ

Original: Never disclose your seed.
Translation: 당신의 씨앗을 공개하지 마십시오. 웹 사이트에 입력하지 마십시오.

Original: Success
Translation: 성ㄱ

────────────────────────────────────────
LOCALE: lv_LV (4 entries)
────────────────────────────────────────

Original: Could not close channel:
Translation: Neizdevās atvērt kanālu:

Original: From
Translation: No

Original: Outputs
Translation: Izvadi

Original: Type
Translation: Tips

────────────────────────────────────────
LOCALE: nb_NO (15 entries)
────────────────────────────────────────

Original: A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.
Translation: A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.

Original: Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"
Translation: Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"

Original: Password was updated successfully
Translation: Password was updated successfully

Original: Select file to export your private keys to
Translation: Select file to export your private keys to

Original: Select which language is used in the GUI (after restart).
Translation: Select which language is used in the GUI (after restart).

Original: The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.
Translation: The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.

Original: The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the 'History' tab.
Translation: The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the 'History' tab.

Original: The following addresses were added
Translation: The following addresses were added

Original: The following inputs could not be imported
Translation: The following inputs could not be imported

Original: This seed will allow you to recover your wallet in case of computer failure.
Translation: This seed will allow you to recover your wallet in case of computer failure.

Original: To make sure that you have properly saved your seed, please retype it here.
Translation: To make sure that you have properly saved your seed, please retype it here.

Original: Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?
Translation: Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?

Original: Your wallet history has been successfully exported.
Translation: Your wallet history has been successfully exported.

Original: To make sure that you have properly saved your seed, please retype it here.
Translation: To make sure that you have properly saved your seed, please retype it here.

Original: This seed will allow you to recover your wallet in case of computer failure.
Translation: This seed will allow you to recover your wallet in case of computer failure.

────────────────────────────────────────
LOCALE: nl_NL (4 entries)
────────────────────────────────────────

Original: Clear
Translation: Wissen

Original: Reporting Bugs
Translation: Bugs melden

Original: [Clear]
Translation: [Wissen]

Original: tasks
Translation: taken

────────────────────────────────────────
LOCALE: pl_PL (1 entries)
────────────────────────────────────────

Original: Banner
Translation: Transparent

────────────────────────────────────────
LOCALE: pt_BR (6 entries)
────────────────────────────────────────

Original: Amount too low
Translation: Quantidade muito pequena

Original: Could not resolve LNURL
Translation: Não foi possível resolver LNURL

Original: This plugin requires installation of additional dependencies.
Translation: Esta opção só está disponível no Windows

Original: unknown
Translation: desconhecido

Original: Enter the list of private keys to sweep into this wallet
Translation: Erro: chave(s) privada(s) inválida(s)

Original: Amount received onchain
Translation: Quantidade recebida nos canais

────────────────────────────────────────
LOCALE: pt_PT (1 entries)
────────────────────────────────────────

Original: Block height
Translation: Peso do bloco

────────────────────────────────────────
LOCALE: ro_RO (3 entries)
────────────────────────────────────────

Original: Broadcasting transaction...
Translation: Tranzactie pe lungime de unda mare...

Original: Seed Type
Translation: Trimite tip

Original: Seed Type
Translation: Trimite tip

────────────────────────────────────────
LOCALE: ru_RU (8 entries)
────────────────────────────────────────

Original: &Flip horizontally
Translation: &Flip horizontally

Original: &New contact
Translation: &New contact

Original: - an arbitrary on-chain script, e.g.:
Translation: - an arbitrary on-chain script, e.g.:

Original: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.
Translation: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.

Original: A backup does not contain information about your local balance in the channel.
Translation: A backup does not contain information about your local balance in the channel.

Original: All outputs are non-change is_mine
Translation: All outputs are non-change is_mine

Original: At block height: {}
Translation: At block height: {}

Original: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.
Translation: A CPFP is a transaction that sends an unconfirmed output back to yourself, with a high fee. The goal is to have miners confirm the parent transaction in order to get the fee attached to the child transaction.

────────────────────────────────────────
LOCALE: sk_SK (1 entries)
────────────────────────────────────────

Original: watching only
Translation: len

────────────────────────────────────────
LOCALE: sv_SE (3 entries)
────────────────────────────────────────

Original: &Plot
Translation: Rita

Original: &View
Translation: &Visa

Original: Build Date
Translation: Versionsdatum

────────────────────────────────────────
LOCALE: th_TH (47 entries)
────────────────────────────────────────

Original: &Addresses
Translation: 1Ph4yWX6g7pJk6pgf9fFnQ7fDG8ioCFrLU

Original: &Documentation
Translation: Ion

Original: &Donate to server
Translation: Dfu

Original: &Encrypt/decrypt message
Translation: Fot

Original: &Export
Translation: Tam

Original: &File
Translation: Fil

Original: &From file
Translation: From fil

Original: &From text
Translation: F. T

Original: &From the blockchain
Translation: F. Ba

Original: &Help
Translation: Sh

Original: &Labels
Translation: La

Original: &Load transaction
Translation: Lo

Original: &New/Restore
Translation: Rest

Original: &Official website
Translation: Ww

Original: &Password
Translation: Snu all

Original: &Plugins
Translation: Piu

Original: &Recently open
Translation: &Recently open

Original: 2. Align your Revealer borderlines to the dashed lines on the top and left.
Translation: 2. Align your Revealer borderlines to the dashed lines on the top and left.

Original: 4. Type the numbers in the software
Translation: 4. Type the numbers in the software

Original: A backup is saved automatically when generating a new wallet.
Translation: A backup is saved automatically when generating a new wallet.

Original: A copy of your wallet file was created in
Translation: A copy of your wallet file was created in

Original: A few examples
Translation: A few examples

Original: A library is probably missing.
Translation: A library is probably missing.

Original: A small fee will be charged on each transaction that uses the remote server. You may check and modify your billing preferences once the installation is complete.
Translation: A small fee will be charged on each transaction that uses the remote server. You may check and modify your billing preferences once the installation is complete.

Original: A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.
Translation: A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.

Original: Accept Word
Translation: Accept Word

Original: Acquisition price
Translation: Acquisition price

Original: Add a cosigner to your multi-sig wallet
Translation: Add a cosigner to your multi-sig wallet

Original: Additional fees
Translation: Additional fees

Original: Additional {} satoshis are going to be added.
Translation: Additional {} satoshis are going to be added.

Original: Address is frozen
Translation: Adresa e blocată

Original: Address not in wallet.
Translation: Address not in wallet.

Original: Also, dust is not kept as change, but added to the fee.
Translation: Also, dust is not kept as change, but added to the fee.

Original: Amount for OP_RETURN output must be zero.
Translation: Amount for OP_RETURN output must be zero.

Original: Amount to be sent
Translation: Amount to be sent

Original: An uninitialized Digital Bitbox is detected.
Translation: An uninitialized Digital Bitbox is detected.

Original: An unnamed {}
Translation: An unnamed {}

Original: Are you SURE you want to wipe the device?\nYour wallet still has bitcoins in it!
Translation: Are you SURE you want to wipe the device?\nYour wallet still has bitcoins in it!

Original: Are you sure you want to erase the Digital Bitbox?
Translation: Are you sure you want to erase the Digital Bitbox?

Original: Are you sure you want to proceed?
Translation: Are you sure you want to proceed?

Original: Are you sure you want to remove this transaction and {} child transactions?
Translation: Are you sure you want to remove this transaction and {} child transactions?

Original: Are you sure you want to remove this transaction?
Translation: Are you sure you want to remove this transaction?

Original: Blockchain
Translation: Address

Original: Broadcasting transaction...
Translation: Broadcasting transaction...

Original: Child Pays for Parent
Translation: Child Pays for Parent

Original: Child pays for parent
Translation: Child Pays for Parent

Original: A few examples
Translation: A few examples

────────────────────────────────────────
LOCALE: tr_TR (4 entries)
────────────────────────────────────────

Original: Import
Translation: Dışa aktar

Original: Use this dialog to change your password.
Translation: Parola değiştirmek için lütfen e-mail adresini kontrol et

Original: Import
Translation: Dışa aktar

Original: Import
Translation: Dışa aktar

────────────────────────────────────────
LOCALE: uk_UA (2 entries)
────────────────────────────────────────

Original: Script type and Derivation path
Translation: 31/5000\nТип сценарію і шлях виведення

Original: To create a spending wallet, please enter a master private key (xprv/yprv/zprv).
Translation: Щоб створити гаманець для оплати, введіть свій відкритий майстер-ключ (xpub/ypub/zpub).

────────────────────────────────────────
LOCALE: zh_TW (2 entries)
────────────────────────────────────────

Original: 2. Align your Revealer borderlines to the dashed lines on the top and left.
Translation: 2. Align your Revealer borderlines to the dashed lines on the top and left.

Original: Your wallet history has been successfully exported.
Translation: 您的錢包歷史記錄已經成功匯入。

@f321x

f321x commented Feb 20, 2026

Copy link
Copy Markdown
Member Author

I reviewed the translations flagged by the LLM, removed them manually and blocked some of the translators that did obvious nonsense.

Observations made:

  • Words get mixed up often ("Import"/"Export", "Private"/"Public")
  • People copy paste the English string into the translation
  • Languages get mixed up, e.g. polish string in japanese translation.
  • Artifacts like &(View) or placeholders like {} get lost or moved out of context in the translation.

I think proofreaders and LLM post processing would increase the translation quality a lot.

We should update the Crowdin project description with some hints/guidelines for translators, e.g. "Please also consider proofreading, it is important", "please don't remove placeholders like {}`, so they are aware of these things.

What do you think of the following description (first paragraph is copy pasted from crowdin):


Electrum is an easy to use Bitcoin client. It protects you from losing coins in a backup mistake or computer failure, because your wallet can be recovered from a secret phrase that you can write on paper or learn by heart. There is no waiting time when you start the client, because it does not download the Bitcoin blockchain.

Notes for translators:

  • Keep placeholders like {}, %s, and %d in the same contextual place — the program will insert data into them at runtime.
  • Don't remove & characters from strings — they mark keyboard shortcuts in the user interface. Keep them in a logical position for your language.
  • Avoid translating technical Bitcoin terms (e.g., "seed," "private key") unless there is a well-established equivalent in your language.
  • Try to keep translations concise, especially for buttons and menu items, to avoid UI overflow.
  • Proofreading helps! Even if your language is already translated, please consider proofreading existing translations. Contact us on IRC (#electrum on libera.chat) for proofreading permissions or if you need context for ambiguous strings.

@f321x
f321x marked this pull request as draft February 20, 2026 09:46
@f321x f321x mentioned this pull request Feb 20, 2026
@f321x f321x closed this Apr 30, 2026
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.

1 participant