Compare commits

23 Commits

Author SHA1 Message Date
cedric cac88c39b2 Merge pull request 'feat: add social network field to artiste' (#3) from feat/improve-artist-page into master
Déploiement API BETA / build (push) Successful in 2m4s
Déploiement API PROD / build (push) Successful in 2m5s
Déploiement API BETA / deploy (push) Successful in 45s
Déploiement API PROD / deploy (push) Successful in 51s
Reviewed-on: #3
2026-06-26 17:36:33 +00:00
cedric fcd1e737ab chore: regenerate types after rezoSosyal field
Déploiement API BETA / build (push) Successful in 2m9s
Déploiement API BETA / deploy (push) Successful in 45s
Vérification PR / build (pull_request) Successful in 2m5s
Vérification PR / deploy-beta (pull_request) Successful in 46s
2026-06-26 12:23:41 +04:00
cedric a10d854ee8 feat: add social network component and artiste field 2026-06-26 12:23:29 +04:00
cedric cf2f4de06c Merge pull request 'feat: rendre le dépôt configurable via variables d'env' (#2) from feat/improve-custom into master
Déploiement API BETA / build (push) Successful in 2m13s
Déploiement API PROD / build (push) Successful in 2m6s
Déploiement API BETA / deploy (push) Successful in 45s
Déploiement API PROD / deploy (push) Successful in 55s
Reviewed-on: #2
2026-06-26 04:59:05 +00:00
cedric 36da183404 ci: deploy beta on all branches including master
Déploiement API BETA / build (push) Successful in 2m6s
Vérification PR / build (pull_request) Successful in 2m11s
Déploiement API BETA / deploy (push) Successful in 45s
Vérification PR / deploy-beta (pull_request) Successful in 44s
2026-06-26 07:26:04 +04:00
cedric 58fd049d03 ci: checkout correct branch before deploy
Déploiement API BETA / build (push) Successful in 2m13s
Vérification PR / build (pull_request) Successful in 2m6s
Déploiement API BETA / deploy (push) Successful in 46s
Vérification PR / deploy-beta (pull_request) Successful in 44s
2026-06-26 07:14:13 +04:00
cedric 0a0772eea3 ci: deploy beta on any branch except master 2026-06-26 07:12:55 +04:00
cedric 7fea170597 chore: regenerate types after isExclusiveArtist rename
Vérification PR / build (pull_request) Successful in 2m7s
Vérification PR / deploy-beta (pull_request) Successful in 1m5s
2026-06-26 00:36:34 +04:00
cedric 029ba3cc90 refactor: rename isOKIAwtis to isExclusiveArtist 2026-06-26 00:33:45 +04:00
cedric c8dd6e9c4a feat: use WEBSITE_URL in publication notification emails 2026-06-26 00:33:42 +04:00
cedric afb38a067b feat: use STRAPI_ADMIN_SITE_NAME for admin panel titles 2026-06-26 00:33:32 +04:00
cedric 2540c04782 feat: add titrePhare
Déploiement API PROD / build (push) Successful in 2m8s
Déploiement API PROD / deploy (push) Successful in 53s
2026-06-20 18:37:23 +04:00
cedric 03e2449be3 feat: add isOKIAwtis
Déploiement API PROD / build (push) Successful in 2m11s
Déploiement API PROD / deploy (push) Successful in 56s
2026-06-20 06:08:42 +04:00
cedric ad034a9a6f feat: add karaokeDesktopUrl
Déploiement API PROD / build (push) Successful in 2m4s
Déploiement API PROD / deploy (push) Successful in 56s
2026-06-18 00:33:06 +04:00
cedric 6db371d513 feat: add karaokeUrl
Déploiement API PROD / build (push) Successful in 2m7s
Déploiement API PROD / deploy (push) Successful in 49s
2026-06-17 23:46:56 +04:00
cedric ea0f56c202 feat: add IsNewRelease
Déploiement API PROD / build (push) Successful in 2m10s
Déploiement API PROD / deploy (push) Successful in 50s
2026-06-17 08:49:14 +04:00
cedric 95156de4ca feat: add bulkTranslate to export
Déploiement API PROD / build (push) Successful in 2m9s
Déploiement API PROD / deploy (push) Successful in 51s
2026-06-15 20:19:53 +04:00
cedric 20e701b754 feat: add bulkTranslate 2026-06-15 20:19:30 +04:00
cedric 615d3fafa0 feat: add langueSource to parole
Déploiement API PROD / build (push) Successful in 2m6s
Déploiement API PROD / deploy (push) Successful in 49s
2026-06-12 14:10:05 +04:00
cedric a000af5c8b feat: add custom route to export data for LLM
Déploiement API PROD / build (push) Successful in 2m8s
Déploiement API PROD / deploy (push) Successful in 58s
2026-06-11 19:07:18 +04:00
cedric b565224fcd feat: add pawol
Déploiement API PROD / build (push) Successful in 2m1s
Déploiement API PROD / deploy (push) Successful in 46s
2026-06-02 02:54:56 +04:00
cedric f7dcad115d update package metadata
Déploiement API PROD / build (push) Successful in 1m59s
Déploiement API PROD / deploy (push) Successful in 43s
2026-05-18 18:32:48 +04:00
cedric 4e41518cf4 Merge pull request 'CI/CD - Mise en place des workflows Gitea Actions' (#1) from dev into master
Déploiement API PROD / build (push) Successful in 2m6s
Déploiement API PROD / deploy (push) Successful in 1m0s
Reviewed-on: #1
2026-05-16 07:55:45 +00:00
14 changed files with 510 additions and 21 deletions
+3
View File
@@ -6,6 +6,9 @@ PORT=1337
STRAPI_URL=
STRAPI_ADMIN_URL=/admin
# Branding (affiché dans l'interface admin Strapi — rebuild requis)
STRAPI_ADMIN_SITE_NAME=OKI
APP_KEYS=
API_TOKEN_SALT=
ADMIN_JWT_SECRET=
+2 -3
View File
@@ -2,8 +2,6 @@ name: Déploiement API BETA
run-name: ${{ gitea.actor }} déploie API BETA
on:
push:
branches:
- dev
jobs:
build:
@@ -39,7 +37,8 @@ jobs:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
cd ${{ secrets.DEPLOY_PATH }}
git pull --ff-only origin dev
git fetch origin
git checkout -B ${{ gitea.ref_name }} origin/${{ gitea.ref_name }}
corepack enable
yarn install --frozen-lockfile
NODE_ENV=production yarn build
+86
View File
@@ -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
```
## 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
### `/awtis`
@@ -77,6 +97,72 @@ ___
### `/paroles/count`
- `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
- `GET` : Exporter les paroles et traductions au format JSONL ou JSON pour l'entraînement de modèles LLM
**Paramètres de requête :**
| Paramètre | Valeurs acceptées | Défaut | Description |
|-----------|-------------------|--------|-------------|
| `type` | `pairs` \| `instruct` | `pairs` | Format des exemples d'entraînement |
| `lang` | `fr,en,es,de,it` | toutes | Langues cibles à inclure (séparées par des virgules) |
| `format` | `jsonl` \| `json` | `jsonl` | Format de la réponse |
**Type `pairs`** — corpus parallèle source/cible, adapté aux modèles de traduction :
```json
{"source_lang":"ka","target_lang":"fr","source":"Mwen ka palé épi ou…","target":"Je suis en train de te parler…","title":"Titre","artists":["Artiste"]}
```
**Type `instruct`** — format instruction/chat, adapté au fine-tuning de modèles d'instruction :
```json
{"messages":[{"role":"system","content":"Tu es un expert en langue KA…"},{"role":"user","content":"Tradui an fransé :\n\nMwen ka palé épi ou…"},{"role":"assistant","content":"Je suis en train de te parler…"}]}
```
**Format `jsonl`** : la première ligne contient les métadonnées (champ `_metadata: true`). Pour les filtrer :
```bash
jq 'select(._metadata | not)' export.jsonl
```
**Métadonnées incluses** (`_metadata: true` en JSONL, clé `metadata` en JSON) :
| Champ | Description |
|-------|-------------|
| `exported_at` | Horodatage de l'export |
| `total_paroles` | Nombre de paroles traitées |
| `total_pairs` | Nombre d'exemples d'entraînement générés |
| `languages` | Nombre de paires par langue |
| `missing_translations` | Paroles avec des traductions manquantes, par langue |
| `non_ka_transcriptions` | Paroles dont la transcription est suspectée d'être dans une autre langue (ex. français) |
**Exemple :**
```bash
curl -H "Authorization: Bearer <token>" \
"https://api.pawol.nu/api/paroles/export?type=instruct&lang=fr,en&format=jsonl" \
-o dataset.jsonl
```
## License
Copyright (C) 2024 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE (OKI)
+4 -4
View File
@@ -5,13 +5,13 @@
"version": "0.1.0",
"license": "AGPL-3.0",
"author": {
"name": "Cédric Famibelle-Pronzola",
"email": "contact@cedric-pronzola.dev",
"url": "https://cedric-pronzola.dev"
"name": "ORGANSATION KA INTERNATIONALE",
"email": "kontak@o-k-i.net",
"url": "https://o-k-i.net"
},
"repository": {
"type": "git",
"url": "git+https://codeberg.org/OKI/api.pawol.nu.git"
"url": "git+https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE/api.pawol.nu"
},
"scripts": {
"dev": "strapi develop",
+4 -4
View File
@@ -13,14 +13,14 @@ export default {
locales: ['fr'],
translations: {
fr: {
'Auth.form.welcome.subtitle': 'Connectez-vous à votre compte OKI API',
'Auth.form.welcome.title': 'Bienvenue sur OKI API !',
'Auth.form.welcome.subtitle': `Connectez-vous à votre compte ${process.env.STRAPI_ADMIN_SITE_NAME || 'OKI'} API`,
'Auth.form.welcome.title': `Bienvenue sur ${process.env.STRAPI_ADMIN_SITE_NAME || 'OKI'} API !`,
'LeftMenu.navbrand.title': 'Tableau de bord',
'LeftMenu.navbrand.workplace': 'Menu',
},
en: {
'Auth.form.welcome.subtitle': 'Log in to your OKI API account',
'Auth.form.welcome.title': 'Welcome to OKI API !',
'Auth.form.welcome.subtitle': `Log in to your ${process.env.STRAPI_ADMIN_SITE_NAME || 'OKI'} API account`,
'Auth.form.welcome.title': `Welcome to ${process.env.STRAPI_ADMIN_SITE_NAME || 'OKI'} API !`,
'LeftMenu.navbrand.title': 'Dashboard',
'LeftMenu.navbrand.workplace': 'Workplace',
}
@@ -57,6 +57,20 @@
},
"musicBrainzUrl": {
"type": "string"
},
"isExclusiveArtist": {
"type": "boolean",
"default": false
},
"titrePhare": {
"type": "relation",
"relation": "manyToOne",
"target": "api::parole.parole"
},
"rezoSosyal": {
"type": "component",
"repeatable": true,
"component": "social.rezo-sosyal"
}
}
}
@@ -144,6 +144,13 @@ module.exports = {
let {data} = event.params
const {documentId} = data
if (data.isNewRelease === true) {
await strapi.db.query('api::parole.parole').updateMany({
where: { isNewRelease: true },
data: { isNewRelease: false },
})
}
const previousParoles = await strapi.db.query('api::parole.parole').findOne({
where: {documentId},
populate: {difference: true, artistes: true}
@@ -186,7 +193,7 @@ module.exports = {
strapi.plugins['email'].services.email.send({
from: process.env.SMTP_FROM,
to: previousData.user.email,
subject: `Publication de "${previousData.titre}" sur pawol.nu`,
subject: `Publication de "${previousData.titre}" sur ${(process.env.WEBSITE_URL || 'https://pawol.nu').replace(/^https?:\/\//, '')}`,
text: `Le titre que vous avez soumis, "${previousData.titre}" a été publié sur le site.
Vous pouvez le trouver à l'adresse ${process.env.WEBSITE_URL}/paroles/${previousData.slug}
Merci pour votre contribution ❤️`,
@@ -199,7 +206,7 @@ module.exports = {
strapi.plugins['email'].services.email.send({
from: process.env.SMTP_FROM,
to: previousData.userAdmin.email,
subject: `Publication de "${previousData.titre}" sur pawol.nu`,
subject: `Publication de "${previousData.titre}" sur ${(process.env.WEBSITE_URL || 'https://pawol.nu').replace(/^https?:\/\//, '')}`,
text: `Le titre que vous avez soumis, "${previousData.titre}" a été publié sur le site.
Vous pouvez le trouver à l'adresse ${process.env.WEBSITE_URL}/paroles/${previousData.slug}.
Merci pour votre contribution ❤️`,
@@ -65,18 +65,18 @@
},
"traductions": {
"type": "component",
"repeatable": false,
"component": "trad.traductions"
"component": "trad.traductions",
"repeatable": false
},
"streamVideo": {
"type": "component",
"repeatable": true,
"component": "url.liens"
"component": "url.liens",
"repeatable": true
},
"streamAudio": {
"type": "component",
"repeatable": true,
"component": "store.album"
"component": "store.album",
"repeatable": true
},
"commentaires": {
"type": "relation",
@@ -88,8 +88,8 @@
},
"difference": {
"type": "component",
"repeatable": true,
"component": "difference.paroles-diff"
"component": "difference.paroles-diff",
"repeatable": true
},
"gadeEmbed": {
"type": "string"
@@ -118,6 +118,40 @@
"videos",
"files"
]
},
"pawol": {
"type": "media",
"multiple": false,
"allowedTypes": [
"files"
]
},
"langueSource": {
"type": "enumeration",
"enum": ["ka", "fr", "en", "es", "de", "it"],
"default": "ka"
},
"sourceOriginale": {
"type": "relation",
"relation": "manyToOne",
"target": "api::parole.parole",
"inversedBy": "remixes"
},
"remixes": {
"type": "relation",
"relation": "oneToMany",
"target": "api::parole.parole",
"mappedBy": "sourceOriginale"
},
"isNewRelease": {
"type": "boolean",
"default": false
},
"karaokeUrl": {
"type": "string"
},
"karaokeDesktopUrl": {
"type": "string"
}
}
}
+41
View File
@@ -2,7 +2,48 @@
const { createCoreController } = require('@strapi/strapi').factories;
const VALID_LANGS = new Set(['fr', 'en', 'es', 'de', 'it'])
module.exports = createCoreController('api::parole.parole', ({strapi}) => ({
async export(ctx) {
const { type = 'pairs', lang, format = 'jsonl' } = ctx.query
const langs = lang
? lang.split(',').map(l => l.trim()).filter(l => VALID_LANGS.has(l))
: null
if (lang && (!langs || langs.length === 0)) {
return ctx.badRequest('Langue(s) invalide(s). Valeurs acceptées : fr, en, es, de, it.')
}
if (!['pairs', 'instruct'].includes(type)) {
return ctx.badRequest('type invalide. Valeurs acceptées : pairs, instruct.')
}
const paroles = await strapi.service('api::parole.parole').fetchAllParoles()
const { metadata, pairs } = strapi.service('api::parole.parole').buildExport(paroles, type, langs)
if (format === 'json') {
return ctx.send({ metadata, data: pairs })
}
// JSONL : première ligne = métadonnées, suivies des exemples d'entraînement.
// Pour filtrer la ligne de métadonnées : jq 'select(._metadata | not)'
const lines = [
JSON.stringify({ _metadata: true, ...metadata }),
...pairs.map(p => JSON.stringify(p)),
]
ctx.set('Content-Type', 'application/x-ndjson')
ctx.set('Content-Disposition', `attachment; filename="pawol-nu-export-${Date.now()}.jsonl"`)
ctx.body = lines.join('\n')
},
async bulkTranslate(ctx) {
const result = await strapi.service('api::parole.parole').bulkTranslateMissing()
return ctx.send(result)
},
async findOne(documentId) {
const parole = await strapi.documents('api::parole.parole').findOne({
documentId,
+18
View File
@@ -0,0 +1,18 @@
'use strict';
module.exports = {
routes: [
{
method: 'GET',
path: '/paroles/export',
handler: 'parole.export',
config: { policies: [], middlewares: [] },
},
{
method: 'POST',
path: '/paroles/bulk-translate',
handler: 'parole.bulkTranslate',
config: { policies: [], middlewares: [] },
},
],
};
+197
View File
@@ -6,6 +6,44 @@ const Diff = require('diff')
const { createCoreService } = require('@strapi/strapi').factories;
const { ApplicationError } = require("@strapi/utils").errors
const LANG_MAP = {
fr: { field: 'francais', targetLang: 'fr', userPrompt: 'Tradui an fransé' },
en: { field: 'anglais', targetLang: 'en', userPrompt: 'Translate to English' },
es: { field: 'espagnol', targetLang: 'es', userPrompt: 'Traduce al español' },
de: { field: 'allemand', targetLang: 'de', userPrompt: 'Übersetze auf Deutsch' },
it: { field: 'italien', targetLang: 'it', userPrompt: 'Traduci in italiano' },
}
const ALL_LANGS = Object.keys(LANG_MAP)
function stripMarkdown(text) {
if (!text) return ''
return text
.replace(/#{1,6}\s+/g, '')
.replace(/\*\*(.*?)\*\*/gs, '$1')
.replace(/\*(.*?)\*/gs, '$1')
.replace(/__(.*?)__/gs, '$1')
.replace(/_(.*?)_/gs, '$1')
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
.replace(/^[>\-\*\+]\s+/gm, '')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
// Détecte si une transcription est probablement en français plutôt qu'en KA.
// Heuristique : si les pronoms personnels français représentent > 4 % des mots.
const FR_PRONOUNS = new Set(['je', 'tu', 'il', 'elle', 'nous', 'vous', 'ils', 'elles'])
function suspectFrench(text) {
if (!text) return false
const words = text.toLowerCase().match(/\b[a-zàâäéèêëîïôöùûüç]+\b/g) || []
if (words.length < 10) return false
const frCount = words.filter(w => FR_PRONOUNS.has(w)).length
return frCount / words.length > 0.04
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
class Translator {
constructor() {
this.deeplApi = process.env.DEEPL_URL || 'api-free.deepl.com'
@@ -72,6 +110,165 @@ module.exports = createCoreService('api::parole.parole', ({strapi}) => ({
throw new ApplicationError('La transcription doit contenir au moins 10 caractères.')
}
},
async fetchAllParoles() {
const pageSize = 100
let start = 0
const all = []
while (true) {
const batch = await strapi.documents('api::parole.parole').findMany({
status: 'published',
populate: ['artistes', 'traductions'],
fields: ['documentId', 'titre', 'slug', 'transcription', 'annee', 'langueSource'],
limit: pageSize,
start,
})
all.push(...batch)
if (batch.length < pageSize) break
start += pageSize
}
return all
},
buildExport(paroles, type, langs) {
const targetLangs = langs && langs.length ? langs : ALL_LANGS
const pairs = []
const missing = []
const nonKa = []
const langCounts = {}
for (const parole of paroles) {
const source = stripMarkdown(parole.transcription)
const sourceLang = parole.langueSource || 'ka'
const artists = (parole.artistes || []).map(a => a.alias)
const paroleMeta = { title: parole.titre, artists }
if (sourceLang !== 'ka') {
nonKa.push({ documentId: parole.documentId, slug: parole.slug, ...paroleMeta, suspected_lang: sourceLang })
} else if (suspectFrench(source)) {
nonKa.push({ documentId: parole.documentId, slug: parole.slug, ...paroleMeta, suspected_lang: 'fr' })
}
const missingLangs = ALL_LANGS.filter(lang => !parole.traductions?.[LANG_MAP[lang].field])
if (missingLangs.length > 0) {
missing.push({ documentId: parole.documentId, slug: parole.slug, ...paroleMeta, missing: missingLangs })
}
for (const lang of targetLangs) {
const { field, targetLang, userPrompt } = LANG_MAP[lang]
if (lang === sourceLang) continue
const target = stripMarkdown(parole.traductions?.[field])
if (!target) continue
langCounts[lang] = (langCounts[lang] || 0) + 1
if (type === 'instruct') {
const systemPrompt = sourceLang === 'ka'
? 'Tu es un expert en langue KA (créole guadeloupéen/martiniquais). Traduis le texte KA suivant.'
: `Tu es un expert en traduction. Traduis le texte suivant (langue source : ${sourceLang}).`
pairs.push({
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `${userPrompt} :\n\n${source}` },
{ role: 'assistant', content: target },
],
})
} else {
pairs.push({
source_lang: sourceLang,
target_lang: targetLang,
source,
target,
...paroleMeta,
})
}
}
}
const metadata = {
exported_at: new Date().toISOString(),
total_paroles: paroles.length,
total_pairs: pairs.length,
languages: langCounts,
missing_translations: missing,
non_ka_transcriptions: nonKa,
}
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) {
const patch = Diff.createPatch(titre, oldString, newString, 'supprimée', 'ajoutée')
const parsePatch = Diff.parsePatch(patch)
+38
View File
@@ -0,0 +1,38 @@
{
"collectionName": "components_social_rezo_sosyal",
"info": {
"displayName": "Rézo Sosyal",
"icon": "earth",
"description": ""
},
"options": {},
"attributes": {
"plateforme": {
"type": "enumeration",
"enum": [
"Mastodon",
"Peertube",
"Pixelfed",
"Funkwhale",
"Bluesky",
"Instagram",
"Youtube",
"Tiktok",
"Spotify",
"Deezer",
"Applemusic",
"Bandcamp",
"Soundcloud",
"Facebook",
"Twitter",
"Linktree",
"SiteWeb"
],
"required": true
},
"url": {
"type": "string",
"required": true
}
}
}
+35
View File
@@ -25,6 +25,40 @@ export interface DifferenceParolesDiff extends Struct.ComponentSchema {
};
}
export interface SocialRezoSosyal extends Struct.ComponentSchema {
collectionName: 'components_social_rezo_sosyal';
info: {
description: '';
displayName: 'R\u00E9zo Sosyal';
icon: 'earth';
};
attributes: {
plateforme: Schema.Attribute.Enumeration<
[
'Mastodon',
'Peertube',
'Pixelfed',
'Funkwhale',
'Bluesky',
'Instagram',
'Youtube',
'Tiktok',
'Spotify',
'Deezer',
'Applemusic',
'Bandcamp',
'Soundcloud',
'Facebook',
'Twitter',
'Linktree',
'SiteWeb',
]
> &
Schema.Attribute.Required;
url: Schema.Attribute.String & Schema.Attribute.Required;
};
}
export interface StoreAlbum extends Struct.ComponentSchema {
collectionName: 'components_store_albums';
info: {
@@ -84,6 +118,7 @@ declare module '@strapi/strapi' {
export module Public {
export interface ComponentSchemas {
'difference.paroles-diff': DifferenceParolesDiff;
'social.rezo-sosyal': SocialRezoSosyal;
'store.album': StoreAlbum;
'trad.traductions': TradTraductions;
'url.liens': UrlLiens;
+17
View File
@@ -448,6 +448,8 @@ export interface ApiArtisteArtiste extends Struct.CollectionTypeSchema {
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
dateNaissance: Schema.Attribute.Date;
isExclusiveArtist: Schema.Attribute.Boolean &
Schema.Attribute.DefaultTo<false>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
@@ -460,7 +462,9 @@ export interface ApiArtisteArtiste extends Struct.CollectionTypeSchema {
photo: Schema.Attribute.Media<'images'>;
prenom: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
rezoSosyal: Schema.Attribute.Component<'social.rezo-sosyal', true>;
slug: Schema.Attribute.String;
titrePhare: Schema.Attribute.Relation<'manyToOne', 'api::parole.parole'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
@@ -540,6 +544,13 @@ export interface ApiParoleParole extends Struct.CollectionTypeSchema {
>;
forceSlug: Schema.Attribute.Boolean;
gadeEmbed: Schema.Attribute.String;
isNewRelease: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
karaokeDesktopUrl: Schema.Attribute.String;
karaokeUrl: Schema.Attribute.String;
langueSource: Schema.Attribute.Enumeration<
['ka', 'fr', 'en', 'es', 'de', 'it']
> &
Schema.Attribute.DefaultTo<'ka'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
@@ -548,9 +559,15 @@ export interface ApiParoleParole extends Struct.CollectionTypeSchema {
Schema.Attribute.Private;
musicBrainzUrl: Schema.Attribute.String;
okiMizikID: Schema.Attribute.Integer;
pawol: Schema.Attribute.Media<'files'>;
prioriteArtistes: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
remixes: Schema.Attribute.Relation<'oneToMany', 'api::parole.parole'>;
slug: Schema.Attribute.String & Schema.Attribute.Unique;
sourceOriginale: Schema.Attribute.Relation<
'manyToOne',
'api::parole.parole'
>;
streamAudio: Schema.Attribute.Component<'store.album', true>;
streamVideo: Schema.Attribute.Component<'url.liens', true>;
titre: Schema.Attribute.String & Schema.Attribute.Required;