Skip to content
15 changes: 14 additions & 1 deletion src/CONST/MERGE_ATS_PROVIDERS.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import type {ValueOf} from 'type-fest';

import CONST from '.';

// The provider slug keys below are dictated by Merge's API.
type MergeATSProviderEntry = {
/** Human-readable label used in the UI */
displayName: string;

/** Provider logo served from the Merge CDN */
iconUrl: string;

/** The ATS fields the provider exposes that a candidate's default approver can be read from, in display order */
approverFields: ReadonlyArray<ValueOf<typeof CONST.MERGE.ATS_APPROVER_FIELD>>;
};

// Merge supports many more ATS providers than the one below. Add them here as they are rolled out.
// Merge supports many more ATS providers than the ones below. Add them here as they are rolled out.
const MERGE_ATS_PROVIDERS = {
ashby: {
displayName: 'Ashby',
iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Ashby_Square_Logo.png',
approverFields: [CONST.MERGE.ATS_APPROVER_FIELD.RECRUITER, CONST.MERGE.ATS_APPROVER_FIELD.HIRING_MANAGER, CONST.MERGE.ATS_APPROVER_FIELD.RECRUITING_COORDINATOR],
},
greenhouse: {
displayName: 'Greenhouse',
iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Greenhouse_Square_Logo.jpg',
approverFields: [CONST.MERGE.ATS_APPROVER_FIELD.RECRUITER, CONST.MERGE.ATS_APPROVER_FIELD.RECRUITING_COORDINATOR],
},
} as const satisfies Record<string, MergeATSProviderEntry>;

Expand Down
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3759,6 +3759,7 @@ const CONST = {
},
ATS_APPROVER_FIELD: {
RECRUITER: 'recruiter',
HIRING_MANAGER: 'hiringManager',
RECRUITING_COORDINATOR: 'coordinator',
},
ATS_FILTER_TYPE: {
Expand Down
10 changes: 7 additions & 3 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9087,7 +9087,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
dontSeeYourATS: `<muted-text-label>Sie sehen Ihr ATS hier nicht? <a href="#">Fragen Sie Concierge</a>, und wir können es hinzufügen.</muted-text-label>`,
importSettings: 'Import-Einstellungen',
defaultApprover: 'Standardgenehmiger',
approverFields: {recruiter: 'Personalvermittler', recruitingCoordinator: 'Recruiting-Koordinator'},
approverFields: {recruiter: 'Personalvermittler', recruitingCoordinator: 'Recruiting-Koordinator', hiringManager: 'Einstellungsmanager'},
filters: {
description: (providerName: string) => `Wählen Sie aus, welche Mitglieder aus ${providerName} importiert werden. Sie können nach Jobphasen, Tags und Büros auswählen.`,
stages: {
Expand Down Expand Up @@ -9121,8 +9121,12 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
finalApprover: `Endgültige genehmigende Person`,
finalApproverOptional: 'Endgültige Freigabe (optional)',
approvalModeDescription: (providerName: string) => `Legen Sie die genehmigende Person für neue Mitglieder fest, die von ${providerName} nach Expensify importiert werden.`,
approverFieldDescription: (providerName: string) =>
`Wählen Sie die erste approver:ent für Ihre Kandidat:innen: entweder ihre:n Recruiter:in oder ihre:n Koordinator:in, die bzw. der in ${providerName} zugewiesen ist.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `ihre*n ${fieldNames.slice(0, -1).join(', ')} oder ${lastFieldName}` : `entweder ihre ${fieldNames.join('oder deren')}`;
return `Wählen Sie die erste genehmigende Person für Ihre Kandidat:innen: ${fieldList}, zugewiesen in ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Wählen Sie eine einzelne genehmigende Person',
advanced: `Die Personalvermittlerin/der Personalvermittler oder die Koordinatorin/der Koordinator der Kandidatin/des Kandidaten wird zur Genehmigerin/zum Genehmiger ihrer Spesen`,
Expand Down
10 changes: 7 additions & 3 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7990,7 +7990,7 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
dontSeeYourATS: `<muted-text-label>Δεν βλέπετε εδώ το ATS σας; <a href="#">Ρωτήστε το Concierge</a> και μπορούμε να το προσθέσουμε.</muted-text-label>`,
importSettings: 'Ρυθμίσεις εισαγωγής',
defaultApprover: 'Προεπιλεγμένος εγκρίνων',
approverFields: {recruiter: 'Στρατολογητής', recruitingCoordinator: 'Συντονιστής προσλήψεων'},
approverFields: {recruiter: 'Στρατολογητής', recruitingCoordinator: 'Συντονιστής προσλήψεων', hiringManager: 'Υπεύθυνος προσλήψεων'},
filters: {
description: (providerName: string) => `Επιλέξτε ποια μέλη θα εισαχθούν από το ${providerName}. Μπορείτε να επιλέξετε από στάδια εργασίας, ετικέτες και γραφεία.`,
stages: {
Expand Down Expand Up @@ -8024,8 +8024,12 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
finalApprover: `Τελική έγκριση`,
finalApproverOptional: 'Τελικός εγκρίτης (προαιρετικό)',
approvalModeDescription: (providerName: string) => `Ορίστε τον εγκριτή για τα νέα μέλη που εισάγονται από το ${providerName} στο Expensify.`,
approverFieldDescription: (providerName: string) =>
`Επιλέξτε τον πρώτο εγκρίνοντα για τους υποψηφίους σας: είτε τον υπεύθυνο προσλήψεών τους είτε τον συντονιστή τους που έχει οριστεί στο ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `το ${fieldNames.slice(0, -1).join(', ')} τους ή το ${lastFieldName} τους` : `είτε ο/η ${fieldNames.join('ή την/τον/τα')} τους`;
return `Επιλέξτε τον πρώτο εγκριτή για τους υποψηφίους σας: ${fieldList} που έχουν ανατεθεί στο ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Επιλέξτε έναν μόνο εγκρίνοντα',
advanced: `Ο υπεύθυνος προσλήψεων ή ο συντονιστής της υποψηφιότητας γίνεται εγκρίνων των εξόδων τους`,
Expand Down
8 changes: 7 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8267,14 +8267,20 @@ const translations = {
finalApprover: `Final approver`,
finalApproverOptional: 'Final approver (optional)',
approvalModeDescription: (providerName: string) => `Set the approver for new members being imported from ${providerName} to Expensify.`,
approverFieldDescription: (providerName: string) => `Choose the first approver for your candidates: either their Recruiter or their Coordinator assigned in ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `their ${fieldNames.slice(0, -1).join(', ')}, or ${lastFieldName}` : `either their ${fieldNames.join(' or their ')}`;
return `Choose the first approver for your candidates: ${fieldList} assigned in ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Choose a single approver',
advanced: `Candidate's recruiter or coordinator becomes their expense approver`,
custom: 'Manually set approvers in Expensify',
},
approverFields: {
recruiter: 'Recruiter',
hiringManager: 'Hiring manager',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make Ashby approver descriptions include hiring managers

When an Ashby admin chooses Advanced approval, this flow now offers Hiring manager, but both approverFieldDescription and approvalModeDescriptions.advanced still say that only a recruiter or coordinator can become the approver. This makes the new valid choice look contradictory or unsupported; make those descriptions provider-aware (or otherwise include the hiring-manager case for Ashby) while preserving Greenhouse's two-field wording.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuwenmemon currently the First approver subtitle says "either their Recruiter or their Coordinator". What should it say for Ashby?

image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Ashby, I'd say "Choose the first approver for your candidates: their recruiter, hiring manager, or recruiting coordinator in Ashby."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuwenmemon since there will be more ATS added later, do you think it's better to have a common message for them all?

If there are only 2 options (like Greenhouse does), then they will be separated by or only.
Choose the first approver for your candidates: their recruiter or recruiting coordinator
If there are more than 2, then it will be separated by , and or at the end.
Choose the first approver for your candidates: their recruiter, hiring manager, or recruiting coordinator

@DylanDylann DylanDylann Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bernhardoj’s suggestion looks fine to me but I think it would also be fine to keep the old description when there are 2 options:

Choose the first approver for your candidates: either their Recruiter or their Coordinator assigned in Greenhouse.

When there are 3 options, @yuwenmemon’s suggestion looks perfect:

Choose the first approver for your candidates: their recruiter, hiring manager, or recruiting coordinator assigned in Ashby.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I applied @DylanDylann suggestion. @yuwenmemon if that looks good, please run the translation script once again.

recruitingCoordinator: 'Recruiting coordinator',
},
filters: {
Expand Down
9 changes: 7 additions & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7714,7 +7714,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
dontSeeYourATS: `<muted-text-label>¿No ves tu ATS aquí? <a href="#">Pregunta a Concierge</a> y podremos añadirlo.</muted-text-label>`,
importSettings: 'Importar ajustes',
defaultApprover: 'Aprobador predeterminado',
approverFields: {recruiter: 'Reclutador', recruitingCoordinator: 'Coordinador de selección'},
approverFields: {recruiter: 'Reclutador', recruitingCoordinator: 'Coordinador de selección', hiringManager: 'responsable de contratación'},
filters: {
description: (providerName: string) => `Selecciona qué miembros se importan desde ${providerName}. Puedes elegir entre etapas de trabajo, etiquetas y oficinas.`,
stages: {
Expand Down Expand Up @@ -7748,7 +7748,12 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
finalApprover: `Aprobador final`,
finalApproverOptional: 'Aprobador final (opcional)',
approvalModeDescription: (providerName: string) => `Establece el aprobador para los nuevos miembros que se importen desde ${providerName} a Expensify.`,
approverFieldDescription: (providerName: string) => `Elige el primer aprobador para tus candidatos: puede ser su reclutador o su coordinador asignado en ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `su ${fieldNames.slice(0, -1).join(', ')}, o ${lastFieldName}` : `ya sea su ${fieldNames.join('o su')}`;
return `Elige al primer aprobador para tus candidatos: ${fieldList} asignado en ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Elige un único aprobador',
advanced: `El reclutador o coordinador del candidato se convierte en su aprobador de gastos`,
Expand Down
10 changes: 7 additions & 3 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9106,7 +9106,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
dontSeeYourATS: `<muted-text-label>Vous ne voyez pas votre ATS ici ? <a href="#">Demandez à Concierge</a> et nous pourrons l’ajouter.</muted-text-label>`,
importSettings: 'Paramètres d’importation',
defaultApprover: 'Approbateur par défaut',
approverFields: {recruiter: 'Recruteur', recruitingCoordinator: 'Coordinateur recrutement'},
approverFields: {recruiter: 'Recruteur', recruitingCoordinator: 'Coordinateur recrutement', hiringManager: 'Responsable du recrutement'},
filters: {
description: (providerName: string) =>
`Sélectionnez quels membres sont importés depuis ${providerName}. Vous pouvez choisir selon les étapes de poste, les tags et les bureaux.`,
Expand Down Expand Up @@ -9141,8 +9141,12 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
finalApprover: `Approbateur final`,
finalApproverOptional: 'Approbateur final (facultatif)',
approvalModeDescription: (providerName: string) => `Définissez l’approbateur pour les nouveaux membres importés de ${providerName} vers Expensify.`,
approverFieldDescription: (providerName: string) =>
`Choisissez le premier approbateur pour vos candidat·es : soit leur recruteur·se, soit leur coordinateur·rice assigné·e dans ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `leur ${fieldNames.slice(0, -1).join(', ')}, ou leur ${lastFieldName}` : `soit leur ${fieldNames.join('ou leur')}`;
return `Choisissez le premier approbateur pour vos candidats : ${fieldList} assigné dans ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Choisir un seul approbateur',
advanced: `Le recruteur ou le coordinateur du candidat devient la personne qui approuve ses dépenses`,
Expand Down
10 changes: 7 additions & 3 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9030,7 +9030,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
dontSeeYourATS: `<muted-text-label>Non vedi il tuo ATS qui? <a href="#">Chiedi a Concierge</a> e possiamo aggiungerlo.</muted-text-label>`,
importSettings: 'Impostazioni di importazione',
defaultApprover: 'Approvatore predefinito',
approverFields: {recruiter: 'Recruiter', recruitingCoordinator: 'Coordinatore recruiting'},
approverFields: {recruiter: 'Recruiter', recruitingCoordinator: 'Coordinatore recruiting', hiringManager: 'Responsabile delle assunzioni'},
filters: {
description: (providerName: string) => `Seleziona quali membri importare da ${providerName}. Puoi scegliere in base alle fasi lavorative, ai tag e alle sedi.`,
stages: {
Expand Down Expand Up @@ -9064,8 +9064,12 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
finalApprover: `Approvatore finale`,
finalApproverOptional: 'Approvatore finale (facoltativo)',
approvalModeDescription: (providerName: string) => `Imposta il responsabile delle approvazioni per i nuovi membri importati da ${providerName} in Expensify.`,
approverFieldDescription: (providerName: string) =>
`Scegli il primo approvatore per i tuoi candidati: il loro Recruiter oppure il loro Coordinator assegnato in ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `il loro ${fieldNames.slice(0, -1).join(', ')} o ${lastFieldName}` : `o il loro ${fieldNames.join('o il loro')}`;
return `Scegli il primo approvatore per i tuoi candidati: ${fieldList} assegnato in ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Scegli un solo approvatore',
advanced: `Il recruiter o il coordinatore del candidato diventa il suo approvatore di spese`,
Expand Down
10 changes: 7 additions & 3 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8916,7 +8916,7 @@ ${reportName}`,
dontSeeYourATS: `<muted-text-label>ご利用のATSが見つかりませんか?<a href="#">Concierge にお問い合わせ</a>いただければ、追加できます。</muted-text-label>`,
importSettings: 'インポート設定',
defaultApprover: 'デフォルト承認者',
approverFields: {recruiter: '採用担当者', recruitingCoordinator: '採用コーディネーター'},
approverFields: {recruiter: '採用担当者', recruitingCoordinator: '採用コーディネーター', hiringManager: '採用マネージャー'},
filters: {
description: (providerName: string) => `${providerName} からインポートするメンバーを選択してください。採用ステージ、タグ、オフィスから選択できます。`,
stages: {
Expand Down Expand Up @@ -8945,8 +8945,12 @@ ${reportName}`,
finalApprover: `最終承認者`,
finalApproverOptional: '最終承認者(任意)',
approvalModeDescription: (providerName: string) => `${providerName} から Expensify にインポートされる新しいメンバーの承認者を設定します。`,
approverFieldDescription: (providerName: string) =>
`候補者の最初の承認者を選択します。${providerName} で割り当てられているリクルーターまたはコーディネーターのいずれかを指定してください。`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `${fieldNames.slice(0, -1).join(', ')} または ${lastFieldName}` : `または ${fieldNames.join('またはその')} のいずれか`;
return `候補者の最初の承認者を選択してください:${providerName} で割り当てられている ${fieldList}`;
},
approvalModeDescriptions: {
basic: '承認者を 1 名選択してください',
advanced: `候補者のリクルーターまたはコーディネーターが、その候補者の経費承認者になります`,
Expand Down
9 changes: 7 additions & 2 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9008,7 +9008,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
dontSeeYourATS: `<muted-text-label>Zie je je ATS hier niet? <a href="#">Vraag het aan Concierge</a> en we kunnen het toevoegen.</muted-text-label>`,
importSettings: 'Importinstellingen',
defaultApprover: 'Standaardgoedkeurder',
approverFields: {recruiter: 'Recruiter', recruitingCoordinator: 'Coördinator werving'},
approverFields: {recruiter: 'Recruiter', recruitingCoordinator: 'Coördinator werving', hiringManager: 'Wervingsmanager'},
filters: {
description: (providerName: string) => `Selecteer welke leden worden geïmporteerd uit ${providerName}. Je kunt kiezen op basis van functieniveaus, tags en kantoren.`,
stages: {
Expand Down Expand Up @@ -9042,7 +9042,12 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
finalApprover: `Laatste goedkeurder`,
finalApproverOptional: 'Laatste fiatteur (optioneel)',
approvalModeDescription: (providerName: string) => `Stel de fiatteur in voor nieuwe leden die vanuit ${providerName} in Expensify worden geïmporteerd.`,
approverFieldDescription: (providerName: string) => `Kies de eerste fiatteur voor je kandidaten: ofwel hun recruiter of hun coördinator die is toegewezen in ${providerName}.`,
approverFieldDescription: (providerName: string, approverFields: string[]) => {
const fieldNames = approverFields.map((approverField) => approverField.toLowerCase());
const lastFieldName = fieldNames.at(-1) ?? '';
const fieldList = fieldNames.length > 2 ? `hun ${fieldNames.slice(0, -1).join(', ')}, of ${lastFieldName}` : `hun ${fieldNames.join('of hun')}`;
return `Kies de eerste fiatteur voor je kandidaten: ${fieldList} toegewezen in ${providerName}.`;
},
approvalModeDescriptions: {
basic: 'Kies één fiatteur',
advanced: `Recruiter of coördinator van de kandidaat wordt zijn/haar onkostengoedkeurder`,
Expand Down
Loading
Loading