Suppression de liens morts dans la description SEO (#858)
* Personnalise la configuration ESLint pour les scripts * 👽 Déplace la traduction de l'explication SEO sur le simulateur salarié * Ajout d'une fonction rule dans sitePaths Permet de type-checker le nom de la règle pour éviter les liens morts * 👽 Ajoute un test pour la traduction des unités * 👽 Traduction BNC * 👽 fix translationspull/863/head
parent
88dcc5d76a
commit
6545281f01
|
@ -112,6 +112,7 @@
|
|||
"@types/classnames": "^2.2.9",
|
||||
"@types/color-convert": "^1.9.0",
|
||||
"@types/iframe-resizer": "^3.5.7",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
"@types/ramda": "^0.26.33",
|
||||
"@types/raven-for-redux": "^1.1.1",
|
||||
"@types/react": "^16.9.11",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName, nameLeaf } from 'Engine/rules'
|
||||
import { nameLeaf } from 'Engine/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Rule } from 'Types/rule'
|
||||
|
@ -21,8 +21,7 @@ export default function RuleLink({
|
|||
}: RuleLinkProps) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const { color } = useContext(ThemeColorsContext)
|
||||
const newPath =
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(dottedName)
|
||||
const newPath = sitePaths.documentation.rule(dottedName)
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
|
|
@ -10,7 +10,7 @@ import PeriodSwitch from 'Components/PeriodSwitch'
|
|||
import ComparaisonConfig from 'Components/simulationConfigs/rémunération-dirigeant.yaml'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import Value from 'Components/Value'
|
||||
import { encodeRuleName, getRuleFromAnalysis } from 'Engine/rules.js'
|
||||
import { getRuleFromAnalysis } from 'Engine/rules.js'
|
||||
import revenusSVG from 'Images/revenus.svg'
|
||||
import { default as React, useCallback, useContext, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
@ -639,7 +639,7 @@ function RuleValueLink({
|
|||
return !rule ? null : (
|
||||
<Link
|
||||
onClick={() => dispatch(setSituationBranch(getBranchIndex(branch)))}
|
||||
to={sitePaths.documentation.index + '/' + encodeRuleName(rule.dottedName)}
|
||||
to={sitePaths.documentation.rule(rule.dottedName)}
|
||||
>
|
||||
<Value
|
||||
maximumFractionDigits={0}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName, parentName } from 'Engine/rules.js'
|
||||
import { parentName } from 'Engine/rules.js'
|
||||
import { pick, sortBy, take } from 'ramda'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import Highlighter from 'react-highlight-words'
|
||||
|
@ -83,9 +83,7 @@ export default function SearchBar({
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to={sitePaths.documentation.index + '/' + encodeRuleName(dottedName)}
|
||||
>
|
||||
<Link to={sitePaths.documentation.rule(dottedName)}>
|
||||
<Highlighter
|
||||
searchWords={[input]}
|
||||
textToHighlight={title || capitalise0(name) || ''}
|
||||
|
@ -98,13 +96,7 @@ export default function SearchBar({
|
|||
if (selectedOption !== null) {
|
||||
finallyCallback && finallyCallback()
|
||||
return (
|
||||
<Redirect
|
||||
to={
|
||||
sitePaths.documentation.index +
|
||||
'/' +
|
||||
encodeRuleName(selectedOption.dottedName)
|
||||
}
|
||||
/>
|
||||
<Redirect to={sitePaths.documentation.rule(selectedOption.dottedName)} />
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import RuleLink from 'Components/RuleLink'
|
|||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { formatCurrency } from 'Engine/format'
|
||||
import { encodeRuleName } from 'Engine/rules'
|
||||
import { isEmpty, isNil } from 'ramda'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
@ -199,13 +198,13 @@ const Target = ({ target, initialRender }) => {
|
|||
|
||||
let Header = ({ target }) => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const ruleLink =
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(target.dottedName)
|
||||
return (
|
||||
<span className="header">
|
||||
<span className="texts">
|
||||
<span className="optionTitle">
|
||||
<Link to={ruleLink}>{target.title || target.name}</Link>
|
||||
<Link to={sitePaths.documentation.rule(target.dottedName)}>
|
||||
{target.title || target.name}
|
||||
</Link>
|
||||
</span>
|
||||
<p>{target.summary}</p>
|
||||
</span>
|
||||
|
|
|
@ -23,7 +23,7 @@ export default function Targets() {
|
|||
<Link
|
||||
title="Quel est calcul ?"
|
||||
style={{ color: colors.color }}
|
||||
to={sitePaths.documentation.index + '/' + dottedName}
|
||||
to={sitePaths.documentation.rule(dottedName)}
|
||||
className="explanation"
|
||||
>
|
||||
{emoji('📖')}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName, findRuleByDottedName } from 'Engine/rules'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
@ -33,12 +33,7 @@ export default function Namespace({ dottedName, flatRules, color }) {
|
|||
|
||||
return (
|
||||
<li style={style} key={fragments.join()}>
|
||||
<Link
|
||||
style={style}
|
||||
to={
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(ruleName)
|
||||
}
|
||||
>
|
||||
<Link style={style} to={sitePaths.documentation.rule(ruleName)}>
|
||||
{rule.icons && <span>{emoji(rule.icons)} </span>}
|
||||
{ruleText}
|
||||
</Link>
|
||||
|
|
|
@ -2,11 +2,7 @@ import { ThemeColorsContext } from 'Components/utils/colors'
|
|||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import Value from 'Components/Value'
|
||||
import knownMecanisms from 'Engine/known-mecanisms.yaml'
|
||||
import {
|
||||
encodeRuleName,
|
||||
findRuleByDottedName,
|
||||
findRuleByNamespace
|
||||
} from 'Engine/rules'
|
||||
import { findRuleByDottedName, findRuleByNamespace } from 'Engine/rules'
|
||||
import { isEmpty } from 'ramda'
|
||||
import React, { Suspense, useContext, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
@ -227,11 +223,7 @@ function NamespaceRulesList({ namespaceRules }) {
|
|||
color: colors.textColorOnWhite,
|
||||
textDecoration: 'underline'
|
||||
}}
|
||||
to={
|
||||
sitePaths.documentation.index +
|
||||
'/' +
|
||||
encodeRuleName(r.dottedName)
|
||||
}
|
||||
to={sitePaths.documentation.rule(r.dottedName)}
|
||||
>
|
||||
{r.title || r.name}
|
||||
</Link>
|
||||
|
|
|
@ -134,11 +134,7 @@ export function Leaf({
|
|||
<span className={classNames(classes, 'leaf')}>
|
||||
{dottedName && (
|
||||
<span className="nodeHead">
|
||||
<Link
|
||||
to={
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(dottedName)
|
||||
}
|
||||
>
|
||||
<Link to={sitePaths.documentation.rule(dottedName)}>
|
||||
<span className="name">
|
||||
{rule.title || capitalise0(name)} {filter}
|
||||
</span>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
"Salariat ":
|
||||
" CDD ":
|
||||
" événements": "_"
|
||||
" motif": "saisonnier"
|
||||
" engagement employeur complément formation": "non"
|
||||
" durée contrat": "2"
|
||||
|
||||
" contrat aidé": "non"
|
||||
" salaire de base": "1481"
|
||||
" congés non pris": "3"
|
|
@ -1,2 +0,0 @@
|
|||
Le script externalize.js va parcourir la base de règles, puis va traduire automatiquement (Google trad) une liste d'attributs les plus importants dans le fichier externalized.yaml.
|
||||
Les traductions déjà existantes réalisées par un humain sont conservées. En effet, la traduction automatique préfixe les traductions par ~~, ce qui lui permet aussi de repérer ce qui a déjà été traduit avec soin.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,15 @@
|
|||
fr:
|
||||
heure_plural: heures
|
||||
jour_plural: jours
|
||||
jour ouvré_plural: jours ouvrés
|
||||
semaine_plural: semaines
|
||||
trimestre_plural: trimestres
|
||||
trimestre validé_plural: trimestres validés
|
||||
an_plural: ans
|
||||
employé_plural: employés
|
||||
point_plural: points
|
||||
mois_plural: mois
|
||||
manifestation_plural: manifestations
|
||||
en:
|
||||
heure: hour
|
||||
heure_plural: hours
|
||||
|
@ -14,10 +17,15 @@ en:
|
|||
points_plural: points
|
||||
jour: day
|
||||
jour_plural: days
|
||||
jour ouvré: working day
|
||||
jour ouvré_plural: working days
|
||||
semaine: week
|
||||
semaine_plural: weeks
|
||||
trimestre: quarter
|
||||
trimestre_plural: quarters
|
||||
trimestre validé: validated quarter
|
||||
trimestre validé_plural: validated quarters
|
||||
km: km
|
||||
repas: meal
|
||||
repas_plural: meals
|
||||
employé: employee
|
||||
|
@ -26,3 +34,5 @@ en:
|
|||
an_plural: years
|
||||
mois: month
|
||||
mois_plural: months
|
||||
manifestation: event
|
||||
manifestation_plural: events
|
||||
|
|
|
@ -71,7 +71,7 @@ artiste-auteur . revenus . BNC . charges forfaitaires:
|
|||
titre.en: fixed expenses
|
||||
titre.fr: charges forfaitaires
|
||||
artiste-auteur . revenus . BNC . frais réels:
|
||||
question.en: NBC actual cost regime
|
||||
question.en: BNC real cost regime
|
||||
question.fr: Régime des frais réels BNC
|
||||
résumé.en: >-
|
||||
Amount of your expenses (business expenses, depreciation, etc.) which will
|
||||
|
@ -84,12 +84,12 @@ artiste-auteur . revenus . BNC . frais réels:
|
|||
artiste-auteur . revenus . BNC . micro-bnc:
|
||||
résumé.en: 34% flat-rate tax allowance for business expenses
|
||||
résumé.fr: Abattement forfaitaire fiscal de 34 % au titre des frais professionnels
|
||||
titre.en: Would you like to opt for the micro-BNC diet?
|
||||
titre.en: Would you like to opt-in for the micro-BNC regime?
|
||||
titre.fr: Souhaitez-vous opter pour le régime micro-BNC ?
|
||||
artiste-auteur . revenus . BNC . recettes:
|
||||
résumé.en: The amount of your gross revenue excluding VAT
|
||||
résumé.fr: Le montant de vos recettes brutes hors TVA
|
||||
titre.en: Income in "NBC"
|
||||
titre.en: Income in "BNC" category
|
||||
titre.fr: Revenu en BNC
|
||||
artiste-auteur . revenus . traitements et salaires:
|
||||
résumé.en: The amount excluding VAT of your royalties (pre-deducted revenue)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# We overwrite the default ESLint configuration for NodeJS scripts
|
||||
env:
|
||||
node: true
|
||||
rules:
|
||||
no-console: 0
|
|
@ -10,9 +10,7 @@ const fs = require('fs')
|
|||
const path = require('path')
|
||||
const yaml = require('js-yaml')
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const sourcePath = path.resolve(__dirname, '../règles/base.yaml')
|
||||
// eslint-disable-next-line no-undef
|
||||
const outPath = path.resolve(__dirname, '../types/dottednames.json')
|
||||
|
||||
function persistJsonFileFromYaml() {
|
||||
|
|
|
@ -48,7 +48,6 @@ const fakeData = [
|
|||
}
|
||||
]
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const dataDir = path.resolve(__dirname, '../data/')
|
||||
|
||||
async function main() {
|
||||
|
|
|
@ -4,7 +4,6 @@ import SalaryExplanation from 'Components/SalaryExplanation'
|
|||
import Simulation from 'Components/Simulation'
|
||||
import salariéConfig from 'Components/simulationConfigs/salarié.yaml'
|
||||
import { IsEmbeddedContext } from 'Components/utils/embeddedContext'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import urlIllustrationNetBrutEn from 'Images/illustration-net-brut-en.png'
|
||||
import urlIllustrationNetBrut from 'Images/illustration-net-brut.png'
|
||||
|
@ -15,7 +14,7 @@ import { useDispatch } from 'react-redux'
|
|||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
export default function Salarié() {
|
||||
const { t, i18n } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isEmbedded = React.useContext(IsEmbeddedContext)
|
||||
return (
|
||||
|
@ -42,56 +41,95 @@ export default function Salarié() {
|
|||
</h1>
|
||||
<div style={{ margin: '2rem' }} />
|
||||
<SalarySimulation />
|
||||
{!isEmbedded && (
|
||||
<Markdown
|
||||
source={i18n.language === 'fr' ? seoExplanations : seoExplanationsEn}
|
||||
/>
|
||||
)}
|
||||
{!isEmbedded && <SeoExplanations />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const seoExplanations = `
|
||||
## Calculer son salaire net
|
||||
function SeoExplanations() {
|
||||
const { t, i18n } = useTranslation()
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
|
||||
Lors de l'entretien d'embauche l'employeur propose en général une rémunération exprimée en « brut ». Le montant annoncé inclut ainsi les cotisations salariales, qui servent à financer la protection sociale du salarié et qui sont retranchées du salaire « net » perçu par le salarié.
|
||||
return (
|
||||
<Trans i18nKey="simulateurs.salarié.page.explication seo">
|
||||
<h2>Calculer son salaire net</h2>
|
||||
|
||||
Vous pouvez utiliser notre simulateur pour convertir le **salaire brut en net** : il vous suffit pour cela saisir la rémunération annoncée dans la case salaire brut. La simulation peut-être affinée en répondant aux différentes questions (sur le CDD, statut cadre, etc.).
|
||||
<p>
|
||||
Lors de l'entretien d'embauche l'employeur propose en général une
|
||||
rémunération exprimée en « brut ». Le montant annoncé inclut ainsi les
|
||||
cotisations salariales, qui servent à financer la protection sociale du
|
||||
salarié et qui sont retranchées du salaire « net » perçu par le salarié.
|
||||
</p>
|
||||
<p>
|
||||
Vous pouvez utiliser notre simulateur pour convertir le{' '}
|
||||
<strong>salaire brut en net</strong> : il vous suffit pour cela saisir
|
||||
la rémunération annoncée dans la case salaire brut. La simulation
|
||||
peut-être affinée en répondant aux différentes questions (sur le CDD,
|
||||
statut cadre, etc.).
|
||||
</p>
|
||||
<img
|
||||
src={
|
||||
i18n.language === 'fr'
|
||||
? urlIllustrationNetBrut
|
||||
: urlIllustrationNetBrutEn
|
||||
}
|
||||
css={`
|
||||
width: 100%;
|
||||
`}
|
||||
/>
|
||||
<p>
|
||||
Par ailleurs depuis 2019, l'
|
||||
<Link to={sitePaths.documentation.rule('impôt . impôt sur le revenu')}>
|
||||
impôt sur le revenu
|
||||
</Link>{' '}
|
||||
est prélevé à la source. Pour ce faire, la direction générale des
|
||||
finances publiques (DGFiP) transmet à l'employeur le taux d'imposition
|
||||
calculé à partir de la déclaration de revenu du salarié. Si ce taux est
|
||||
inconnu, par exemple lors d'une première année d'activité, l'employeur
|
||||
utilise le{' '}
|
||||
<Link
|
||||
to={sitePaths.documentation.rule(
|
||||
"impôt . taux neutre d'impôt sur le revenu"
|
||||
)}
|
||||
>
|
||||
taux neutre
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
|
||||
![Salaire net et brut](${urlIllustrationNetBrut})
|
||||
<h2>Coût d'embauche</h2>
|
||||
|
||||
Par ailleurs depuis 2019, l'[impôt sur le revenu](/documentation/impôt/impôt-sur-le-revenu) est prélevé à la source. Pour ce faire, la direction générale des finances publiques (DGFiP) transmet à l'employeur le taux d'imposition calculé à partir de la déclaration de revenu du salarié. Si ce taux est inconnu, par exemple lors d'une première année d'activité, l'employeur utilise le [taux neutre](/documentation/impôt/impôt-sur-le-revenu-au-taux-neutre). C'est aussi le taux neutre que nous utilisons dans le simulateur pour calculer le « net après impôt » qui est versé sur le compte bancaire du salarié.
|
||||
<p>
|
||||
Si vous cherchez à embaucher, vous pouvez calculer le coût total de la
|
||||
rémunération de votre salarié, ainsi que les montants de cotisations
|
||||
patronales et salariales correspondants. Cela vous permet de définir le
|
||||
niveau de rémunération en connaissant le montant global de charge que
|
||||
cela représente pour votre entreprise.
|
||||
</p>
|
||||
|
||||
Attention : L'impôt sur le revenu est calculé à partir du [salaire net imposable](/documentation/contrat-salarié/rémunération/net-imposable) qui inclut certaines cotisations non déductibles et les avantages en nature en plus du salaire net.
|
||||
<p>
|
||||
En plus du salaire, notre simulateur prend en compte le calcul des
|
||||
avantages en nature (téléphone, véhicule de fonction, etc.), ainsi que
|
||||
la mutuelle santé obligatoire.
|
||||
</p>
|
||||
|
||||
## Coût d'embauche
|
||||
|
||||
Si vous cherchez à embaucher, vous pouvez calculer le coût total de la rémunération de votre salarié, ainsi que les montants de cotisations patronales et salariales correspondants. Cela vous permet de définir le niveau de rémunération en connaissant le montant global de charge que cela représente pour votre entreprise.
|
||||
|
||||
En plus du salaire, notre simulateur prend en compte le calcul des avantages en nature (téléphone, véhicule de fonction, etc.), ainsi que la mutuelle santé obligatoire.
|
||||
|
||||
Il existe des [aides différées](/documentation/aides-employeur) à l'embauche qui ne sont pas prises en compte par notre simulateur, vous pouvez les retrouver sur [le portail officiel](http://www.aides-entreprises.fr).`
|
||||
|
||||
const seoExplanationsEn = `
|
||||
## Calculate your net salary
|
||||
|
||||
During the job interview, the employer usually talks about the "gross" salary. The amount announced thus includes employee contributions, which are used to finance the employee's social protection and are deducted from the "net" salary received by the employee.
|
||||
|
||||
You can use our simulator to convert the gross salary into a net salary: all you need to do is enter the remuneration announced in the gross salary input. The simulation can be refined by answering the different questions (on the CDD, “Cadre” status, etc.).
|
||||
|
||||
![Gross and net salaries](${urlIllustrationNetBrutEn})
|
||||
|
||||
In addition, since 2019, the income tax is withholded. To do this, the “Direction Générale des Finances Publiques” (DGFiP) sends the employer the tax rate calculated from the employee's income tax return. If this rate is unknown, for example for the first year of employment, the employer uses the neutral rate. It is also the neutral rate that we use in the simulator to calculate the "after-tax net" that is paid into the employee's bank account.
|
||||
|
||||
Note: Income tax is calculated on the net taxable salary, which includes certain non-deductible contributions and benefits in kind in addition to the net salary.
|
||||
|
||||
## Hiring cost
|
||||
|
||||
If you are looking to hire, you can calculate the total cost of your employee's compensation, as well as the corresponding employer and employee contributions. This allows you to define the level of compensation by knowing the total amount of expense that this represents for your company.
|
||||
|
||||
In addition to the salary, our simulator takes into account the calculation of benefits in kind (telephone, company car, etc.), as well as the mandatory health insurance.
|
||||
|
||||
There are deferred hiring aids that are not taken into account by our simulator, you can find them on the official portal.`
|
||||
<p>
|
||||
Il existe des{' '}
|
||||
<Link
|
||||
to={sitePaths.documentation.rule('contrat salarié . aides employeur')}
|
||||
>
|
||||
aides différées
|
||||
</Link>{' '}
|
||||
à l'embauche qui ne sont pas toutes prises en compte par notre
|
||||
simulateur, vous pouvez les retrouver sur{' '}
|
||||
<a href="http://www.aides-entreprises.fr" target="_blank">
|
||||
le portail officiel
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
||||
export let SalarySimulation = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { encodeRuleName } from 'Engine/rules'
|
||||
import { map, reduce, toPairs, zipObj } from 'ramda'
|
||||
import { LegalStatus } from 'Selectors/companyStatusSelectors'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import i18n from '../../i18n'
|
||||
|
||||
export const LANDING_LEGAL_STATUS_LIST: Array<LegalStatus> = [
|
||||
|
@ -29,7 +31,7 @@ interface HasIndex {
|
|||
}
|
||||
|
||||
type SitePathsObject<T> = {
|
||||
[key in keyof T]: string | SitePathsObject<T[key]>
|
||||
[key in keyof T]: string | Function | SitePathsObject<T[key]>
|
||||
}
|
||||
|
||||
function constructSitePaths<T extends SitePathsObject<HasIndex>>(
|
||||
|
@ -41,6 +43,8 @@ function constructSitePaths<T extends SitePathsObject<HasIndex>>(
|
|||
...map(value =>
|
||||
typeof value === 'string'
|
||||
? root + index + value
|
||||
: typeof value === 'function'
|
||||
? (...args: any) => root + index + String(value(...args))
|
||||
: constructSitePaths(root + index, value as any)
|
||||
)(sitePaths as any)
|
||||
} as any
|
||||
|
@ -115,7 +119,8 @@ export const constructLocalizedSitePath = (language: string) => {
|
|||
nouveautés: t('path.nouveautés', '/nouveautés'),
|
||||
documentation: {
|
||||
exemples: t('path.documentation.exemples', '/exemples'),
|
||||
index: t('path.documentation.index', '/documentation')
|
||||
index: t('path.documentation.index', '/documentation'),
|
||||
rule: (dottedName: DottedName) => '/' + encodeRuleName(dottedName)
|
||||
},
|
||||
integration: {
|
||||
index: t('path.integration.index', '/intégration'),
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { expect } from 'chai'
|
||||
import { uniq } from 'ramda'
|
||||
import { rulesFr } from '../source/engine/rules'
|
||||
import { parseAll } from '../source/engine/traverse'
|
||||
import unitsTranslations from '../source/locales/units.yaml'
|
||||
|
||||
it('has translation for all base units', () => {
|
||||
const rules = parseAll(rulesFr)
|
||||
const units = uniq(
|
||||
Object.keys(rules).reduce(
|
||||
(prev, name) => [
|
||||
...prev,
|
||||
...(rules[name].unit?.numerators ?? []),
|
||||
...(rules[name].unit?.denumerators ?? [])
|
||||
],
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
const blackList = ['€', '%']
|
||||
const translatedKeys = Object.keys(unitsTranslations.en)
|
||||
const missingTranslations = units.filter(
|
||||
unit => ![...translatedKeys, ...blackList].includes(unit)
|
||||
)
|
||||
expect(missingTranslations).to.be.empty
|
||||
})
|
|
@ -1192,6 +1192,11 @@
|
|||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/js-yaml@^3.12.2":
|
||||
version "3.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a"
|
||||
integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug==
|
||||
|
||||
"@types/node@*":
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.1.tgz#6d11a8c2d58405b3db9388ab740106cbfa64c3c9"
|
||||
|
|
Loading…
Reference in New Issue