Compare commits
2 Commits
615d3fafa0
...
95156de4ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
95156de4ca
|
|||
|
20e701b754
|
@@ -56,6 +56,26 @@ Si la variable JWT_SECRET n'est pas renseignée, elle est générée automatique
|
|||||||
yarn && yarn build && yarn dev
|
yarn && yarn build && yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Tokens API
|
||||||
|
|
||||||
|
Les endpoints marqués ⚙️ **Token requis** nécessitent un token API Strapi.
|
||||||
|
|
||||||
|
**Créer le token** : Administration Strapi → *Settings → API Tokens → Create new API Token*
|
||||||
|
|
||||||
|
| Endpoint | Type de token | Permissions requises |
|
||||||
|
|----------|---------------|----------------------|
|
||||||
|
| `GET /paroles/export` | Custom | Parole → `export` |
|
||||||
|
| `POST /paroles/bulk-translate` | Custom | Parole → `bulkTranslate` |
|
||||||
|
|
||||||
|
Pour un token couvrant les deux endpoints, créer un token de type **Custom** et cocher dans la section *Parole* : `export` et `bulkTranslate`.
|
||||||
|
|
||||||
|
Le token est à passer dans le header HTTP :
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Point d'accès
|
## Point d'accès
|
||||||
|
|
||||||
### `/awtis`
|
### `/awtis`
|
||||||
@@ -77,6 +97,28 @@ ___
|
|||||||
### `/paroles/count`
|
### `/paroles/count`
|
||||||
- `GET` : Récupère le nombre de texte
|
- `GET` : Récupère le nombre de texte
|
||||||
|
|
||||||
|
### `/paroles/bulk-translate` ⚙️ Token requis
|
||||||
|
- `POST` : Traduit automatiquement via DeepL toutes les paroles ayant une source française (`traductions.francais` ou `langueSource: fr`) vers les langues manquantes (EN, ES, DE, IT). Ne modifie pas les traductions déjà existantes.
|
||||||
|
|
||||||
|
**Réponse :**
|
||||||
|
```json
|
||||||
|
{"translated": 42, "skipped": 18, "errors": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Champ | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `translated` | Nombre de traductions ajoutées |
|
||||||
|
| `skipped` | Paroles ignorées (pas de source FR ou déjà complètes) |
|
||||||
|
| `errors` | Erreurs DeepL avec `documentId`, `titre` et `lang` |
|
||||||
|
|
||||||
|
**Exemple :**
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer <token>" \
|
||||||
|
"https://api.pawol.nu/api/paroles/bulk-translate"
|
||||||
|
```
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
### `/paroles/export` ⚙️ Token requis
|
### `/paroles/export` ⚙️ Token requis
|
||||||
- `GET` : Exporter les paroles et traductions au format JSONL ou JSON pour l'entraînement de modèles LLM
|
- `GET` : Exporter les paroles et traductions au format JSONL ou JSON pour l'entraînement de modèles LLM
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ module.exports = createCoreController('api::parole.parole', ({strapi}) => ({
|
|||||||
ctx.body = lines.join('\n')
|
ctx.body = lines.join('\n')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async bulkTranslate(ctx) {
|
||||||
|
const result = await strapi.service('api::parole.parole').bulkTranslateMissing()
|
||||||
|
return ctx.send(result)
|
||||||
|
},
|
||||||
|
|
||||||
async findOne(documentId) {
|
async findOne(documentId) {
|
||||||
const parole = await strapi.documents('api::parole.parole').findOne({
|
const parole = await strapi.documents('api::parole.parole').findOne({
|
||||||
documentId,
|
documentId,
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ module.exports = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: '/paroles/export',
|
path: '/paroles/export',
|
||||||
handler: 'parole.export',
|
handler: 'parole.export',
|
||||||
config: {
|
config: { policies: [], middlewares: [] },
|
||||||
policies: [],
|
|
||||||
middlewares: [],
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
path: '/paroles/bulk-translate',
|
||||||
|
handler: 'parole.bulkTranslate',
|
||||||
|
config: { policies: [], middlewares: [] },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ function suspectFrench(text) {
|
|||||||
return frCount / words.length > 0.04
|
return frCount / words.length > 0.04
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
class Translator {
|
class Translator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.deeplApi = process.env.DEEPL_URL || 'api-free.deepl.com'
|
this.deeplApi = process.env.DEEPL_URL || 'api-free.deepl.com'
|
||||||
@@ -196,6 +198,77 @@ module.exports = createCoreService('api::parole.parole', ({strapi}) => ({
|
|||||||
return { metadata, pairs }
|
return { metadata, pairs }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async bulkTranslateMissing() {
|
||||||
|
const TARGET_LANGS = [
|
||||||
|
{ lang: 'en', field: 'anglais', deeplTarget: 'EN', suffix: '\n\n(Translated by DeepL)' },
|
||||||
|
{ lang: 'es', field: 'espagnol', deeplTarget: 'ES', suffix: '\n\n(Traducido por DeepL)' },
|
||||||
|
{ lang: 'de', field: 'allemand', deeplTarget: 'DE', suffix: '\n\n(Übersetzt von DeepL)' },
|
||||||
|
{ lang: 'it', field: 'italien', deeplTarget: 'IT', suffix: '\n\n(Tradotto da DeepL)' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const pageSize = 100
|
||||||
|
let start = 0
|
||||||
|
const all = []
|
||||||
|
while (true) {
|
||||||
|
const batch = await strapi.documents('api::parole.parole').findMany({
|
||||||
|
status: 'published',
|
||||||
|
populate: ['traductions'],
|
||||||
|
fields: ['documentId', 'slug', 'titre', 'transcription', 'langueSource'],
|
||||||
|
limit: pageSize,
|
||||||
|
start,
|
||||||
|
})
|
||||||
|
all.push(...batch)
|
||||||
|
if (batch.length < pageSize) break
|
||||||
|
start += pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const translator = new Translator()
|
||||||
|
const translated = []
|
||||||
|
const skipped = []
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
for (const parole of all) {
|
||||||
|
const sourceFR = parole.traductions?.francais
|
||||||
|
|| (parole.langueSource === 'fr' ? parole.transcription : null)
|
||||||
|
|
||||||
|
if (!sourceFR) { skipped.push(parole.slug); continue }
|
||||||
|
|
||||||
|
const missing = TARGET_LANGS.filter(({ field }) => !parole.traductions?.[field])
|
||||||
|
if (missing.length === 0) { skipped.push(parole.slug); continue }
|
||||||
|
|
||||||
|
const { id: _id, ...tradData } = parole.traductions || {}
|
||||||
|
const updatedTrad = { ...tradData }
|
||||||
|
const addedLangs = []
|
||||||
|
|
||||||
|
for (const { lang, field, deeplTarget, suffix } of missing) {
|
||||||
|
try {
|
||||||
|
await sleep(700)
|
||||||
|
const result = await translator.get('FR', deeplTarget, sourceFR)
|
||||||
|
const text = result?.translations?.[0]?.text
|
||||||
|
if (text) {
|
||||||
|
updatedTrad[field] = text + suffix
|
||||||
|
addedLangs.push(lang)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errors.push({ slug: parole.slug, lang: deeplTarget, error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedLangs.length > 0) {
|
||||||
|
await strapi.documents('api::parole.parole').update({
|
||||||
|
documentId: parole.documentId,
|
||||||
|
data: { traductions: updatedTrad },
|
||||||
|
})
|
||||||
|
await strapi.documents('api::parole.parole').publish({
|
||||||
|
documentId: parole.documentId,
|
||||||
|
})
|
||||||
|
translated.push({ slug: parole.slug, langs: addedLangs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { translated, skipped, errors }
|
||||||
|
},
|
||||||
|
|
||||||
parolesDiff(titre = '', oldString, newString) {
|
parolesDiff(titre = '', oldString, newString) {
|
||||||
const patch = Diff.createPatch(titre, oldString, newString, 'supprimée', 'ajoutée')
|
const patch = Diff.createPatch(titre, oldString, newString, 'supprimée', 'ajoutée')
|
||||||
const parsePatch = Diff.parsePatch(patch)
|
const parsePatch = Diff.parsePatch(patch)
|
||||||
|
|||||||
Reference in New Issue
Block a user