LorrainJS: la librairie qui parle Lorrain
Traduire du code en lorrain ? Personne n'y avait jamais pensé. Alors nous l'avons fait. Et l'histoire de la librairie LorrainJS est une bonne occasion pour découvrir les prototypes JavaScript, les expressions régulières, et la publication de package npm.
NDLR: La plupart des employés de Marmelab, dont le siège social est à Nancy, sont Lorrains. Mais tout le monde parle français couramment, et nous recrutons partout en France.
Traduire du code en lorrain, pourquoi pas ?
Comment qu'c'est, gros ? Ça geht's mohl ? Nous c'est cool. Schpeun la region, c'est la plus michto ! Nous les Lorrains on a des idées de chtarbés ! Mais la plupart des pelos y z'entravent rien à ce qu'on baragouine et nous on on galère à les comprendre. Alors pour qu'on évite de se maraver la fratz, on a pas fait nos feuts, on a schaff et on a inventé un outil trop schteuff : LorrainJS.
Suite à une décision de la cour d'appel de Nancy, nous sommes dans l'obligation de traduire le passage précédent dans un langage soutenu et non partisan.
Comment vous portez-vous chers lecteurs ? Pour ce qui est de nos personnes, nous allons bien. Regardez cette grisaille persistante et cette architecture rudimentaire, c'est la Lorraine, une terre que vous ne souhaitez pas forcément connaître. Nous les provinciaux, nous n'avons pas de pétrole, mais nous avons des idées tout à fait moyennes. Nous sommes tellement moyens que nous n'arrivons pas à exprimer nos idées et nous ne comprenons pas les idées des autres. Afin d'éviter une quelconque rixe à ce sujet, nous avons pris sur nous et avons travaillé à un outil que vous trouverez inutile : LorrainJS.
Des logs plus lisibles dans vos programmes
Grâce au génie de deux cerveaux en constante ébullition, vous pouvez communiquer facilement avec vos amis développeurs lorrains. Et pour les vrais, vous aurez beaucoup moins de mal à lire les logs de vos applications. Une facilité accrue de lecture des logs, c'est un debuggage plus facile, donc un gain de temps. Ce qui fait plus de temps à dénoyauter les Mirabelles pour une bonne tarte avec de la migaine.
Pour installer cette librairie d'un nouveau genre, il vous suffit de lancer dans votre projet :
yarn add lorrainjs
une fois dans votre projet :
import { lorrainsjs } from 'lorrainjs';
const ljsOptions = {
gros: true,
le: true,
mirabelle: true,
o: true,
};
/* si vous ne précisez pas d'options au moment du initAll(),
toutes les options seront activées par défaut */
lorrainjs.initAll(ljsOptions);
Et la magie prendra alors forme, toutes vos chaines de caractères dans les logs (String, tableau de String ou propriété String d'un objet) seront désormais en Lorrain.
console.log(`Patrick aimerait beaucoup manger des pâtes et des oranges`);
/* Le Patrick aimerait beaucoup mônger des pôtes et des orônges gros */
console.log('degub', { test: 'Bonjour', ignore: 456 });
/* debug gros { test: "Bonjour gros", ignore: 456} */
Vous pourrez retrouver toute la documentation sur GitHub
Une navigation facilitée sur le web
Grâce aux compétences d'un troisième cerveau (merci Guiom), nous avons pu porter LorrainJS dans une extension de navigateur, pour faciliter encore plus la vie de tous les Lorrains.
https://addons.mozilla.org/fr/firefox/addon/traducteur-lorrain/
Prenons par exemple la documentation de React Admin. C'est un outil très complet et la documentation l'est tout autant. Mais elle est parfaitement incompréhensible pour un Lorrain. Une fois l'extension activée, tout cette difficulté de lecture devient un lointain souvenir :
Un traducteur accessible 24h/24
Vous ne souhaitez pas installer de librairie mais souhaitez quand même profiter de la traduction français-lorrain à la demande ? Vous pouvez utiliser notre traducteur en ligne !
https://julienmattiussi.github.io/traducteur-lorrain/
Cet outil utilise le moteur de LorrainJS dans une app front utilisable par tous ! Vos collègues, vos amis, vos familles par alliances... Partagez sans limite !
On regarde sous le capot
La surcharge de fonctions natives
Un des points intéressants de la création a été la modification des fonctions natives de log (console.log()
, console.warn()
et console.error()
).
Nous aurions pu nous contenter de proposer nos propres fonctions lorrainLog()
, etc, sans toucher aux fonctions js natives. Mais l'idée était de pouvoir modifier le comportement d'un programme entier d'une simple ligne, sans avoir besoin de tout réécrire.
Et par ailleurs, ça aurait été nettement moins intéressant à concevoir de cette façon.
On a donc réalisé des fonctions d'initialisations (initLog()
, etc) à appeler en début de script, et dont le but est de modifier le fonctionnement de JavaScript.
Le principe même de JS, en tant que language par prototype, est de permettre ce genre d'opérations car les prototypes sont mutables. Il existe de nombreux exemples en ligne détaillant comment modifier la fonction console.log
, généralement en anglais, nous nous sommes largement appuyés dessus (voir les références en fin d'article).
Sur le principe, c'est très rapide, il n'y a qu'a attribuer autre chose à console.log
console.log = () => {
return "j'ai changé console.log gros";
};
Il reste toutefois deux problèmes essentiels :
- on veut pouvoir disposer des paramètres envoyés à la fonction,
- on ne veut pas perdre le fonctionnement d'origine de
log
, seulement le modifier
Pour récupérer les paramètres, on utilise l'objet arguments
C'est un pseudo Array
qui contient tous les arguments passés à la fonction dans laquelle on se trouve. Comme ce n'est pas un vrai Array
, pas de map
ou de forEach
possible, mais on s'en sort avec une manipulation un peu plus subtile :
const originalLog = console.log;
console.log = () => {
// Le tableau qui va contenir les paramètres modifiés
var msgs = [];
// On parcourt `arguments` pour récupérer tous les paramètres passés à `log`
while (arguments.length) {
// On extrait les items un par un
const argument = [].shift.call(arguments);
// On modifie l'item avant de l'ajouter au nouveau tableau
msgs.push(translate(argument));
}
// `apply` va simplement appliquer au premier paramètre `console`
// la fonction `originalLog`, avec le second paramètre `msg` comme liste d'arguments
originalLog.apply(console, msgs);
};
Finalement, ça tient en peu de lignes, et le procédé peut être appliqué à absolument tout ce que l'on souhaite, pas seulement des logs.
Les regexp de la mort
Les expression régulières, c'est bien connu, c'est le boss final du développement. Sauf que vous le croisez au tout début de votre aventure. Mais une fois les patterns compris, sans être trivial, il devient accessible.
Dans lorrainJS, nous avons adopté une approche assez candide de la "traduction" en passant par des patterns de recherche et donc, par des regexp.
Prenons comme exemple la phrase suivante :
Michel est parti acheter des pommes.
Nous avons besoin de gérer les cas suivants :
- Remplacer n'importe quel fruit par Mirabelle,
- ajouter
gros
en fin de phrase, - ajouter un
le/la/du
devant les prénoms, - remplacer les a par des o.
Tous ces cas peuvent être en partie gérés par des regexp. Elles nous permettront de cibler précisément, grâce à certaines règles, la portion de texte qui doit être modifiée.
Le cas le moins ardu, c'est le remplacement d'un fruit par Mirabelle (oui, Mirabelle, comme toutes les divinités, mérite une majuscule). Nous avons donc un dictionnaire de tous les fruits qui va nous permettre de faire une recherche dans le texte.
Voici donc en JS ce que donne le remplacement d'un fruit :
const replaceFruitNameWithMirabelle = text => {
/* la chaîne de caractères dans la requelle nous voulons appliquer le remplacement */
let newText = text;
fruits.map(fruit => {
/* construction de la regexp */
const fruitRegExp = new RegExp(fruit, 'ig');
/* application de la regexp et remplacement grâce à la méthode replace de javascript */
newText = newText.replace(fruitRegExp, 'mirabelle');
});
return newText;
};
L'avantage de l'objet Regexp de JS, c'est qu'il accepte deux format : une chaîne de caractère (comme ici) ou une regexp plus classique (type /[]$/g
)
Ici donc, nous avons juste à passer le nom du fruit sous forme d'une chaîne. Le deuxième paramètre du constructeur ig
sert à définir les modificateurs (flags) de la regexp.
Ainsi nous pouvons modifier le comportement de la recherche. le g
(global) définit que la recherche sera globale et que chaque itération sera prise en compte (sans ce modificateur, seule la première correspondance sera considérée).
Le modificateur i
sert quant à lui à rendre la recherche insensible à la casse (et permettre de trouver des noms de fruits en minuscule ou en majuscule).
Cette regexp appliquée à notre chaîne de départ donnera :
const phrase = 'Michel est parti acheter des pommes.';
const mirabellized = replaceFruitNameWithMirabelle(phrase);
console.log(mirabellized);
/* "Michel est parti acheter des mirabelles." */
Passons au niveau supérieur avec l'ajout du "gros" en fin de phrase. Notre regexp doit "traduire" la situation suivante : je suis à la fin d'une phrase.
En français, la fin d'une phrase est caractérisée par une ponctuation (! ? . ;
) et doit être précédée par un mot (ceci évitera les transformations de ...
en gros. gros. gros.
).
Comme nous allons avoir plusieurs "sections" dans notre regexp, nous utilisons les groupes, représentés dans une regexp par des ()
et les classes de caractères représentés par les []
.
Notre premier groupe ressemblera à ceci :
([!?.;])
Il nous faut ensuite définir un groupe qui représente une chaîne de caractère.
([a-zA-z1-9]+)
Assemblons tout ça :
/([a-zA-z1-9]+)([!?.;])/g;
Et pour que l'on puisse voir rapidement le fonctionnement de notre regexp, nous allons utiliser l'outil regex101. Cet outil dispose d'une référence avec exemples, une validation de la regex, une explication de celle-ci et une zone de test.
Notre regexp sélectionne bien la fin de notre phrase, soit le point précédé d'un mot.
Pour la suite de l'explication, nous allons regarder une dernière regexp qui est le remplacement des "a" par des "o" (car comme tout le monde le sait, les pâtes, en lorrain, ça se prononce pôtes).
Pour ceci, nous avons besoin de sélectionner deux groupes, comme pour la regexp précédente. Nous avons remarqué que les mots qui commencent ou se terminent par un "a", ne sont pas prononcés différemment. C'est bien dans le cas d'un "a" au milieu d'un mot précédé ou suivi d'une consonne que le Lorrain s'exprime différemment. Dans notre exemple le mot "parti" correspond à notre cas, contrairement au mot "acheter".
Faisons comme Michel, allons-y.
Le premier groupe est donc une lettre qui n'est pas une voyelle. Ce qui donne ceci :
([^eiouy ])
Le deuxième groupe est donc notre a
([aAà])
Notre troisième et dernier groupe est identique au premier :
([^eiouy])
Ce qui correspond une fois assemblée à cette regexp :
([^eiouy ])([aAà])([^eiouy])
Parfait ! Ou presque ... Parce que ce n'est pas ce que nous cherchons. Nous souhaitons uniquement sélectionner un a
qui soit entre deux consonnes.
Ici tout est sélectionné. Nous allons donc nous servir des Positive lookahead & positive lookbehind qui permettent de s'assurer de la présence de groupes sans qu'ils ne fassent partie de la sélection.
Ce qui en termes plus compréhensibles revient à ignorer dans la sélection le premier et le dernier groupe.
Pour le premier groupe il sera transformé ainsi :
(?<=[^eiouy ])
Le dernier groupe quand a lui demande un peu plus de ... doigté : il nous faut faire un groupe constitué de la recherche sur le a
et d'une consonne afin d'échapper la consonne de la sélection.
(([aAâ])(?=[^eiouy]))
Et cette nouvelle version semble fonctionner :
Nous pouvons maintenant remplacer les "a" concernés par des "o" et donc parler comme un vrai Lorrain !
Concernant les regexp, le secret est de prendre son temps, de bien découper ses besoins et d'utiliser les bons outils pour se faciliter la vie. Les regexp ont un pouvoir démentiel, facilitent énormément la vie dans le traitement de chaînes de caractères, mais trop peu utilisées, on en oublie vite les codes.
Toutes nos regexp combinées nous permettent d'arrive au résultat escompté :
Le Michel est pôrti acheter des mirabelles gros.
Facile comme une publication de lib
Dernière étape notable de la conception : la publication.
Pourquoi notable ? Parce que oui, on peut passer des années dans le développement sans jamais avoir eu à publier la moindre librairie quelque part. Probablement encore une manifestation de l'ostracisme anti-lorrains qui plane sur notre milieu et que l'on combat ici activement.
J'ai donc été particulièrement surpris de voir à quel point la démarche était rapide et sans effort sur npm :
Créer un compte npm ici si ce n'est pas déjà fait (pas besoin de beaucoup plus qu'une boite mail)
Avoir le cli
npm
(il vient avecnodejs
, donc basiquement soit vous l'avez déjà, soit vous faites sur linux :sudo apt-get install nodejs
)Connecter le cli npm à vote compte :
npm login
puis saisir login et mot de passe.Aller dans le dossier du projet à publier
cd ~/myproj/lorrainjs
Vérifier que les informations nécessaires sont présentes dans le fichier
package.json
(j'y reviens en détail juste en dessous, c'est le seul point vraiment critique)Pour un projet en TypeScript, builder le projet avec la commande
tsc
(par exemple)Enfin, dans le dossier du projet : faites
npm publish
Et voilà, en maximum 7 étapes simples, la librairie est en ligne et accessible à tout le monde (et encore, il y a de grandes chances de commencer quasiment à chaque fois par la 4ème).
On va juste voir le fichier package.json
un peu en détail parce que c'est là que tout se joue. Ce fichier au coeur de presque tout projet JS de nos jours, va contenir outre la définition des dépendances du projet tout un tas d'informations indispensables à npm
pour référencer la librairie.
Je les ai commentées ci-dessous directement dans le corps du fichier JSON (oui, je sais : des commentaires en plein milieu d'un fichier JSON !!! Le sacrilège ! La chouffe!)
{
// Le nom officiel de la librairie. Il doit être disponible sur npmjs.com,
// pensez à contrôler dès le départ. Pour lorrainJS, bizarrement, c'était libre.
"name": "lorrainjs",
// La version du projet. Elle ne peut jamais décrémenter et doit obligatoirement
// être incrémentée pour pouvoir republier le projet. Elle respecte la syntaxe semver.
"version": "1.1.1",
// Le descriptif du projet, il sera visible par les utilisateurs qui
// trouveront votre librairie en ligne, et peut servir en partie au référencement.
"description": "Logger for Lorraine people",
// Le fichier principal d'exécution de la librairie. dist/ ici
// parce qu'on publie le code buildé du projet et pas ses sources.
"main": "dist/index.js",
// Dans le cas d'un projet typescript, la définition des types du projet
"types": "dist/index.d.ts",
// Le répertoire à publier, la version buildée du projet dans notre cas.
"files": ["/dist"],
// L'auteur principal du projet (pour voir votre nom en grand sur internet).
// On ne peut pas en mettre plusieurs.
"author": {
"name": "YavaDeus",
"email": "julienm@marmelab.com",
"url": "https://github.com/JulienMattiussi"
},
// Les autres contributeurs et co-auteurs du projet.
"contributors": [
{
"name": "Aherbeth",
"email": "alexandre@marmelab.com",
"url": "https://github.com/Aherbeth"
}
],
// Le type de licence qui s'applique à vos sources
"license": "MIT",
// Ici, c'est simple, c'est toujours false.
// Un projet privé ne peut pas être publié sur npm,
// mais uniquement sur un repository privé.
"private": false,
// Le lien vers la page principale de votre projet que vous voulez mettre en avant
// (nous, on a mis la page du code source sur github, mais on peut imaginer
// que vous avez une vraie page "commerciale" qui décrit votre projet)
"homepage": "https://github.com/marmelab/lorrainjs",
// Le lien vers les sources du projet
"repository": {
"type": "git",
"url": "git@github.com:marmelab/lorrainjs.git"
},
// Important, les mots-clés qui serviront au référencement du projet par npm
// et aideront les gens à le trouver facilement.
"keywords": [
"lorraine",
"lorrains",
"lorrain",
"gros",
"grosse",
"mirabelle",
"bergamote",
"bergamotte",
"madeleine"
],
...
}
Voilà, maintenant on sait publier une librairie sur npm, et vous aussi.
Conclusion : L'outil de la décennie
Derrière une idée saugrenue, se cache parfois une idée géniale. Mais s'il y a une chose que nous a apporté ce projet fou, c'est que pousser une idée et produire un "MVP", c'est la garantie d'apprendre des choses.
Encore plus important, travailler à deux, c'est aussi l'assurance d'enrichir les compétences et les connaissances de l'autre. Un peu comme la migaine et la mirabelle dans une tarte.
Pour résumer : choisissez quelqu'un avec qui vous avez envie de travailler, prenez votre idée la plus folle, la plus stupide ou idiote et par dessus tout, emmenez ce projet au bout. Vous allez surmonter des difficultés, apprendre et pouvoir présenter un résultat. Et vous aurez peut-être la joie de présenter un outil tout aussi génial, révolutionnaire et indispensable que le nôtre !
Aller gros, faut pas avoir les pounches pour clancher la porte des trucs michtos ! (Martin Luther King, lorrain de souche)
Note: Dans le but de rendre cet article le plus lisible et compréhensible possible pour des non Lorrains, nous nous sommes imposé l'utilisation de la langue française commune et l'adptation du code que vous pourrez trouver dans la lib. Certains exemples ont donc été simplifiés pour supprimer les cas particuliers qui ne sont au final qu'une mise en application un peu plus poussée des concepts expliqués ici.
Pour aller plus loin: