import { useTranslation } from 'react-i18next' import { MetadataSrc } from '@/pages/simulateurs-et-assistants/metadata-src' const rawSitePathsFr = { index: '', assistants: { index: 'assistants', sécuritéSociale: 'sécurité-sociale', formulaireMobilité: 'demande-mobilité', 'recherche-code-ape': 'recherche-code-ape', 'déclaration-charges-sociales-indépendant': 'declaration-charges-sociales-independant', économieCollaborative: { index: 'économie-collaborative', }, 'pour-mon-entreprise': { index: 'pour-mon-entreprise', entreprise: ':entreprise', }, déclarationIndépendant: { index: 'aide-declaration-independants-v2', }, 'choix-du-statut': { index: 'choix-du-statut', 'recherche-activité': 'recherche-activite', 'détails-activité': 'details-activite', commune: 'commune', association: 'association', associé: 'associe', rémunération: 'remuneration', comparateur: 'comparateur', résultat: { index: 'resultat', AE: 'auto-entrepreneur', EURL: 'EURL', SARL: 'SARL', EI: 'entreprise-individuelle', SASU: 'SASU', SAS: 'SAS', SELARL: 'SELARL', SELARLU: 'SELARLU', SELAS: 'SELAS', SELASU: 'SELASU', association: 'association', }, }, }, simulateurs: { index: 'simulateurs', 'coût-création-entreprise': 'cout-creation-entreprise', 'auto-entrepreneur': 'auto-entrepreneur', 'entreprise-individuelle': 'entreprise-individuelle', eirl: 'eirl', sasu: 'sasu', eurl: 'eurl', indépendant: 'indépendant', comparaison: 'comparaison-régimes-sociaux', pamc: 'pamc', salarié: 'salaire-brut-net', 'artiste-auteur': 'artiste-auteur', 'profession-libérale': { index: 'profession-liberale', médecin: 'medecin', pharmacien: 'pharmacien', auxiliaire: 'auxiliaire-medical', 'chirurgien-dentiste': 'chirurgien-dentiste', 'sage-femme': 'sage-femme', avocat: 'avocat', 'expert-comptable': 'expert-comptable', cipav: 'cipav', }, 'chômage-partiel': 'chômage-partiel', is: 'impot-societe', dividendes: 'dividendes', }, nouveautés: { index: 'nouveautés', date: ':date', }, stats: 'statistiques', accessibilité: 'accessibilité', budget: 'budget', simulateursEtAssistants: 'simulateurs-et-assistants', développeur: { index: 'développeur', iframe: 'iframe', library: 'bibliothèque-de-calcul', api: 'api', spreadsheet: 'tableur', }, documentation: { index: 'documentation', }, plan: 'plan-du-site', } as const const rawSitePathsEn = { ...rawSitePathsFr, assistants: { index: 'assistants', sécuritéSociale: 'social-security', formulaireMobilité: 'posting-demand', 'recherche-code-ape': 'search-code-ape', 'déclaration-charges-sociales-indépendant': 'declaration-social-charges-independent', économieCollaborative: { index: 'sharing-economy', votreSituation: 'your-situation', }, 'pour-mon-entreprise': { index: 'for-my-business', entreprise: ':entreprise', }, déclarationIndépendant: { index: 'declaration-aid-independent-v2', imposition: 'taxation', entreprise: 'company', déclaration: 'declaration', cotisations: 'contributions', }, 'choix-du-statut': { index: 'choice-of-status', 'recherche-activité': 'recherche-activite', 'détails-activité': 'details-activite', après: 'after-registration', commune: 'commune', association: 'association', associé: 'partnership', rémunération: 'remuneration', comparateur: 'comparator', résultat: { index: 'result', AE: 'auto-entrepreneur', EURL: 'EURL', SARL: 'SARL', EI: 'entreprise-individuelle', SASU: 'SASU', SAS: 'SAS', SELARL: 'SELARL', SELARLU: 'SELARLU', SELAS: 'SELAS', SELASU: 'SELASU', association: 'association', }, }, }, simulateurs: { index: 'calculators', 'coût-création-entreprise': 'coût-création-entreprise', indépendant: 'independant', 'entreprise-individuelle': 'sole-proprietorship', 'auto-entrepreneur': 'auto-entrepreneur', eirl: 'eirl', sasu: 'sasu', eurl: 'eurl', pamc: 'pamc', comparaison: 'social-scheme-comparaison', salarié: 'salary', 'artiste-auteur': 'artist-author', 'chômage-partiel': 'partial-unemployement', 'profession-libérale': { index: 'liberal-profession', médecin: 'doctor', pharmacien: 'pharmacist', auxiliaire: 'medical-auxiliary', 'chirurgien-dentiste': 'dental-surgeon', 'sage-femme': 'midwife', avocat: 'lawyer', 'expert-comptable': 'accountant', cipav: 'cipav', }, is: 'corporate-tax', dividendes: 'dividends', }, nouveautés: { index: 'news', date: ':date', }, stats: 'statistics', accessibilité: 'accessibility', simulateursEtAssistants: 'simulators-and-assistants', développeur: { ...rawSitePathsFr.développeur, index: 'developer', library: 'library', api: 'api', spreadsheet: 'spreadsheet', }, plan: 'sitemap', } as const /** * Le but des types suivants est d'obtenir un typage statique des chaînes de caractères * comme "simulateurs.auto-entrepreneur" utilisés comme identifiants des routes (via les pathId dans metadata-src.ts). * Cela permet de ne pas avoir de faute dans les clés comme 'aide-embauche' au lieu de 'aides-embauche' */ // Transfrom string type like PathToType<'simulateurs.auto-entrepreneur', number> // into { simulateurs : { auto-entrepreneur: number }} type PathToType = T extends `${infer U}.${infer V}` ? { [key in U]: V extends string ? PathToType : never } : { [key in T]: W } // Transform type A | B into A & B type UnionToIntersection = ( T extends unknown ? (x: T) => void : never ) extends (x: infer R) => void ? R : never // Union of pathId type PathIds = MetadataSrc[keyof MetadataSrc]['pathId'] type RequiredPath = Required>> // If there is a type error here, check rawSitePathsFr object matches the metadata-src.ts pathId const checkedSitePathsFr = rawSitePathsFr satisfies RequiredPath // If there is a type error here, check rawSitePathsEn object matches the metadata-src.ts pathId const checkedSitePathsEn = rawSitePathsEn satisfies RequiredPath // eslint-disable-next-line @typescript-eslint/no-unused-vars type SitePathsFr = typeof checkedSitePathsFr // eslint-disable-next-line @typescript-eslint/no-unused-vars type SitePathsEn = typeof checkedSitePathsEn type GenericSitePath = { [key: string]: string | GenericSitePath } const encodeRelativeSitePaths = (base: T): T => { const sitepaths = Object.entries(base).reduce( (obj, [key, val]) => ({ ...obj, [key]: typeof val === 'string' ? val : encodeRelativeSitePaths(val), }), {} as T ) return sitepaths } const encodedRelativeSitePaths = encodeRelativeSitePaths({ fr: rawSitePathsFr, en: rawSitePathsEn, }) const encodedAbsoluteSitePaths = { fr: constructAbsoluteSitePaths(rawSitePathsFr), en: constructAbsoluteSitePaths(rawSitePathsEn), } export const relativeSitePaths = encodedRelativeSitePaths export const absoluteSitePaths = encodedAbsoluteSitePaths export type RelativeSitePaths = (typeof relativeSitePaths)[keyof typeof relativeSitePaths] export type AbsoluteSitePaths = (typeof absoluteSitePaths)[keyof typeof absoluteSitePaths] export const useSitePaths = (lang?: T) => { const { language } = useTranslation().i18n lang ??= language as T return { relativeSitePaths: relativeSitePaths[lang], absoluteSitePaths: absoluteSitePaths[lang], } } type SitePath = { [key: string]: string | SitePath } & { index: string } type SitePathBuilt = { [K in keyof T]: T[K] extends string ? K extends 'index' ? `${Root}${T[K]}` extends '' ? '/' : `${Root}${T[K]}` : T extends { index: string } ? `${Root}${T['index']}/${T[K]}` : `${Root}${T[K]}` : SitePathBuilt< T[K] extends SitePath ? T[K] : never, T extends { index: string } ? `${Root}${T['index']}/` : `${Root}` > } function constructAbsoluteSitePaths( obj: T, root = '' ): SitePathBuilt { const { index } = obj const entries = Object.entries(obj) return Object.fromEntries( entries.map(([k, value]) => [ k, typeof value === 'string' ? root + (k === 'index' ? value : index + '/' + value) || '/' : constructAbsoluteSitePaths(value, root + index + '/'), ]) ) as SitePathBuilt } type Obj = { [k: string]: string | Obj } const deepReduce = ( fn: (acc: string[], val: string, key: string) => string[], initialValue: string[], object: Obj ): string[] => Object.entries(object).reduce( (acc, [key, value]) => typeof value === 'object' ? deepReduce(fn, acc, value) : fn(acc, value, key), initialValue ) type SiteMap = Array export const generateSiteMap = (sitePaths: AbsoluteSitePaths): SiteMap => deepReduce( (paths: Array, path: string) => /\/:/.test(path) ? paths : [...paths, ...[path]], [], sitePaths ) export const alternatePathname = () => { type Lang = 'fr' | 'en' type Sitepath = { [k: string]: string | Sitepath } type LangSitepath = { [k in Lang]: string } type Return = { [k: string]: LangSitepath | Return } const buildSitemap = ( lang: Lang, sitePath: Sitepath, initialValue: Return = {} ): Return => Object.entries(sitePath).reduce( (acc, [key, path]): Return => typeof path === 'object' ? { ...acc, [key]: buildSitemap(lang, path, acc[key] as Return) } : ({ ...acc, [key]: { ...acc[key], [lang]: path } } as Return), initialValue ) const buildPathname = ( sitemap: Return, initialValue: Record> = { fr: {}, en: {} } ) => Object.values(sitemap).reduce( (acc, obj): Record> => typeof obj === 'object' && ('fr' in obj || 'en' in obj) ? { fr: { ...acc.fr, ...('fr' in obj ? { [obj.fr as string]: (obj.en || obj.fr) as string } : null), }, en: { ...acc.en, ...('en' in obj ? { [obj.en as string]: (obj.fr || obj.en) as string } : null), }, } : buildPathname(obj, acc), initialValue ) return buildPathname( buildSitemap( 'en', absoluteSitePaths.en, buildSitemap('fr', absoluteSitePaths.fr) ) ) }