Repository: WeblateOrg/weblate

Issue:
Glossary check not working on spaceless languages
### Describe the issue

When having `check-glossary` enabled, the check always fails on spaceless languages, if the source string has a word in it that is in the glossary.

### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar filed issues in this repository.

### Steps to reproduce the behavior

1. Create glossary entry that actually exists in a source string
2. Translate the source string with the glossary entry to Japanese (or Korean or similar)
3. Enable `check-glossary`
4. Notice a false positive on the check

### Expected behavior

_No response_

### Screenshots

![Bildschirmfoto vom 2024-03-30 17-12-24](https://github.com/WeblateOrg/weblate/assets/11144627/d906ae16-7c70-42f7-943a-ac9f03b0517d)
![Bildschirmfoto vom 2024-03-30 17-12-20](https://github.com/WeblateOrg/weblate/assets/11144627/65995849-2de4-4df8-a05c-230964d257cc)


### Exception traceback

_No response_

### How do you run Weblate?

Docker container

### Weblate versions

 * Weblate: 5.4.3
 * Django: 4.2.11
 * siphashc: 2.4.1
 * translate-toolkit: 3.12.2
 * lxml: 5.1.0
 * pillow: 10.2.0
 * nh3: 0.2.17
 * python-dateutil: 2.9.0.post0
 * social-auth-core: 4.5.3
 * social-auth-app-django: 5.4.0
 * django-crispy-forms: 2.1
 * oauthlib: 3.2.2
 * django-compressor: 4.4
 * djangorestframework: 3.14.0
 * django-filter: 23.5
 * django-appconf: 1.0.6
 * user-agents: 2.2.0
 * filelock: 3.13.3
 * rapidfuzz: 3.7.0
 * openpyxl: 3.1.2
 * celery: 5.3.6
 * django-celery-beat: 2.5.0
 * kombu: 5.3.5
 * translation-finder: 2.16
 * weblate-language-data: 2024.3
 * html2text: 2020.1.16
 * pycairo: 1.26.0
 * PyGObject: 3.48.1
 * diff-match-patch: 20230430
 * requests: 2.31.0
 * django-redis: 5.4.0
 * hiredis: 2.3.2
 * sentry-sdk: 1.43.0
 * Cython: 3.0.9
 * misaka: 2.1.1
 * GitPython: 3.1.42
 * borgbackup: 1.2.7
 * pyparsing: 3.1.2
 * ahocorasick_rs: 0.22.0
 * python-redis-lock: 4.0.0
 * charset-normalizer: 3.3.2
 * Python: 3.12.2
 * Git: 2.39.2
 * psycopg: 3.1.18
 * psycopg-binary: 3.1.18
 * phply: 1.2.6
 * ruamel.yaml: 0.18.6
 * tesserocr: 2.6.2
 * boto3: 1.34.71
 * zeep: 4.2.1
 * aeidon: 1.13
 * iniparse: 0.5
 * mysqlclient: 2.2.4
 * Mercurial: 6.7.1
 * git-svn: 2.39.2
 * git-review: 2.4.0
 * PostgreSQL server: 15.2
 * Database backends: django.db.backends.postgresql
 * PostgreSQL implementation: psycopg3 (binary)
 * Cache backends: default:RedisCache, avatar:FileBasedCache
 * Email setup: django.core.mail.backends.smtp.EmailBackend: mail.your-server.de
 * OS encoding: filesystem=utf-8, default=utf-8
 * Celery: redis://cache:6379/1, redis://cache:6379/1, regular
 * Platform: Linux 5.10.0-28-amd64 (x86_64)

### Weblate deploy checks

```shell
System check identified some issues:

INFOS:
?: (weblate.I028) Backups are not configured, it is highly recommended for production use
	HINT: https://docs.weblate.org/en/weblate-5.4.3/admin/backup.html

System check identified 1 issue (1 silenced).
```


### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/weblate/checks/glossary.py b/weblate/checks/glossary.py
--- a/weblate/checks/glossary.py
+++ b/weblate/checks/glossary.py
@@ -24,17 +24,22 @@ def check_single(self, source, target, unit):
         forbidden = set()
         mismatched = set()
         matched = set()
+        boundary = r"\b" if unit.translation.language.uses_whitespace() else ""
         for term in get_glossary_terms(unit):
             term_source = term.source
             flags = term.all_flags
             expected = term_source if "read-only" in flags else term.target
             if "forbidden" in flags:
-                if re.search(rf"\b{re.escape(expected)}\b", target, re.IGNORECASE):
+                if re.search(
+                    rf"{boundary}{re.escape(expected)}{boundary}", target, re.IGNORECASE
+                ):
                     forbidden.add(term_source)
             else:
                 if term_source in matched:
                     continue
-                if re.search(rf"\b{re.escape(expected)}\b", target, re.IGNORECASE):
+                if re.search(
+                    rf"{boundary}{re.escape(expected)}{boundary}", target, re.IGNORECASE
+                ):
                     mismatched.discard(term_source)
                     matched.add(term_source)
                 else:


Test patch: