[Studio] Importing a 'fr' Story without Labels creates 'en' Labels
- Import a 'fr' Story (upload a Story Dump) to a 'fr' bot application - without importing the Labels
- When one goes to edit the Story, Labels are automatically created for 'en' language

The problem lies in the Answer-to-BotAnswer mapping in `BotAdminService#loadStory`.
diff --git a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt
index cbf48db7e..ff107437e 100644
--- a/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt
+++ b/bot/admin/server/src/main/kotlin/BotAdminVerticle.kt
@@ -668,12 +668,11 @@ open class BotAdminVerticle : AdminVerticle() {
             mapper.writeValueAsString(labels)
         }
 
-        blockingUploadPost(
+        blockingUploadJsonPost(
             "/i18n/import/json",
             botUser,
             simpleLogger("JSON Import Response Labels")
-        ) { context, content ->
-            val labels: List<I18nLabel> = mapper.readValue(content)
+        ) { context, labels: List<I18nLabel> ->
             i18n.save(labels.filter { it.i18n.any { it.validated } }.map {
                 it.copy(
                     _id = it._id.toString().replaceFirst(it.namespace, context.organization).toId(),
diff --git a/bot/admin/server/src/main/kotlin/I18nCsvCodec.kt b/bot/admin/server/src/main/kotlin/I18nCsvCodec.kt
index 8257a7613..9ec28c4ba 100644
--- a/bot/admin/server/src/main/kotlin/I18nCsvCodec.kt
+++ b/bot/admin/server/src/main/kotlin/I18nCsvCodec.kt
@@ -21,6 +21,7 @@ import ai.tock.nlp.admin.CsvCodec
 import ai.tock.nlp.admin.CsvCodec.csvFormat
 import ai.tock.shared.error
 import ai.tock.shared.injector
+import ai.tock.shared.vertx.WebVerticle
 import ai.tock.translator.I18nDAO
 import ai.tock.translator.I18nLabel
 import ai.tock.translator.I18nLocalizedLabel
@@ -42,25 +43,29 @@ object I18nCsvCodec {
 
     private val i18nDAO: I18nDAO by injector.instance()
 
+    internal enum class CsvColumn {
+        Label,
+        Namespace,
+        Category,
+        Language,
+        Interface,
+        Id,
+        Validated,
+        Connector,
+        Alternatives
+    }
+
     fun exportCsv(namespace: String, query: I18LabelQuery? = null): String {
         val sb = StringBuilder()
         val printer = CsvCodec.newPrinter(sb)
-        printer.printRecord(
-            "Label",
-            "Category",
-            "Language",
-            "Interface",
-            "Id",
-            "Validated",
-            "Connector",
-            "Alternatives"
-        )
+        printer.printRecord(CsvColumn.values().map { it.name })
         i18nDAO.getLabels(namespace, query?.toI18nLabelFilter())
             .forEach { label ->
                 label.i18n.forEach { localizedLabel ->
                     printer.printRecord(
                         *(listOf(
                             localizedLabel.label,
+                            label.namespace,
                             label.category,
                             localizedLabel.locale.language,
                             localizedLabel.interfaceType,
@@ -76,26 +81,35 @@ object I18nCsvCodec {
 
     fun importCsv(namespace: String, content: String): Boolean {
         return try {
-            csvFormat()
+            val parsedCsv = csvFormat().withFirstRecordAsHeader()
                 .parse(StringReader(content))
+            val headers = parsedCsv.headerNames
+            val isNamespaceInCsv = headers.contains(CsvColumn.Namespace.name)
+            parsedCsv
                 .records
                 .mapIndexedNotNull { i, it ->
                     if (i == 0) {
                         null
                     } else {
                         I18nLabel(
-                            it[4].toId(),
+                            if (isNamespaceInCsv)
+                                it.get(CsvColumn.Id.name).replaceFirst(it.get(CsvColumn.Namespace.name), namespace).toId()
+                            else
+                                it.get(CsvColumn.Id.name).toId(),
                             namespace,
-                            it[1],
+                            it.get(CsvColumn.Category.name),
                             LinkedHashSet(
                                 listOf(
                                     I18nLocalizedLabel(
-                                        Locale(it[2]),
-                                        UserInterfaceType.valueOf(it[3]),
-                                        it[0],
-                                        it[5]?.toBoolean() ?: false,
-                                        it[6].run { if (isBlank()) null else this },
-                                        if (it.size() < 7) emptyList() else (7 until it.size()).mapNotNull { index -> if (it[index].isNullOrBlank()) null else it[index] }
+                                        Locale(it.get(CsvColumn.Language.name)),
+                                        UserInterfaceType.valueOf(it.get(CsvColumn.Interface.name)),
+                                        it[0], // First header has a strange char before it, simpler with index
+                                        it.get(CsvColumn.Validated.name)?.toBoolean() ?: false,
+                                        it.get(CsvColumn.Connector.name).run { if (isBlank()) null else this },
+                                        if (it.size() < headers.indexOf(CsvColumn.Alternatives.name).also { logger.info { "Alt index is $it" } })
+                                            emptyList()
+                                        else
+                                            (headers.indexOf(CsvColumn.Alternatives.name).also { logger.info { "Alt index is $it" } } until it.size()).mapNotNull { index -> if (it[index].isNullOrBlank()) null else it[index] }
                                     ))
                             )
                         )
@@ -127,6 +141,9 @@ object I18nCsvCodec {
                     i18nDAO.save(it)
                 }
             true
+        } catch (t: IllegalArgumentException) {
+            logger.error(t)
+            WebVerticle.badRequest("Error importing CSV: ${t.message}")
         } catch (t: Throwable) {
             logger.error(t)
             false
diff --git a/bot/admin/server/src/main/kotlin/model/BotConfiguredAnswer.kt b/bot/admin/server/src/main/kotlin/model/BotConfiguredAnswer.kt
index 41bc0ca6a..f5a14d3ad 100644
--- a/bot/admin/server/src/main/kotlin/model/BotConfiguredAnswer.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotConfiguredAnswer.kt
@@ -18,6 +18,7 @@ package ai.tock.bot.admin.model
 
 import ai.tock.bot.admin.answer.AnswerConfigurationType
 import ai.tock.bot.admin.answer.DedicatedAnswerConfiguration
+import java.util.Locale
 
 /**
  *
@@ -27,9 +28,9 @@ class BotConfiguredAnswer(
     val currentType: AnswerConfigurationType,
     val answers: List<BotAnswerConfiguration>
 ) {
-    constructor(conf: DedicatedAnswerConfiguration) : this(
+    constructor(conf: DedicatedAnswerConfiguration, locale: Locale?) : this(
         conf.botConfiguration,
         conf.currentType,
-        conf.answers.mapAnswers()
+        conf.answers.mapAnswers(locale)
     )
 }
\ No newline at end of file
diff --git a/bot/admin/server/src/main/kotlin/model/BotSimpleAnswer.kt b/bot/admin/server/src/main/kotlin/model/BotSimpleAnswer.kt
index 531c921f8..9b13eec74 100644
--- a/bot/admin/server/src/main/kotlin/model/BotSimpleAnswer.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotSimpleAnswer.kt
@@ -20,15 +20,16 @@ import ai.tock.bot.admin.answer.SimpleAnswer
 import ai.tock.translator.I18nLabel
 import ai.tock.translator.I18nLabelValue
 import ai.tock.translator.Translator
+import java.util.Locale
 
 /**
  *
  */
 data class BotSimpleAnswer(val label: I18nLabel, val delay: Long, val mediaMessage: BotMediaMessageDescriptor? = null) {
 
-    constructor(answer: SimpleAnswer) :
+    constructor(answer: SimpleAnswer, locale: Locale?) :
         this(
-            Translator.saveIfNotExist(answer.key),
+            Translator.saveIfNotExist(answer.key, locale),
             answer.delay,
             answer.mediaMessage?.let { BotMediaMessageDescriptor.fromDescriptor(it) }
         )
diff --git a/bot/admin/server/src/main/kotlin/model/BotSimpleAnswerConfiguration.kt b/bot/admin/server/src/main/kotlin/model/BotSimpleAnswerConfiguration.kt
index 0e43d1f12..a559b2edc 100644
--- a/bot/admin/server/src/main/kotlin/model/BotSimpleAnswerConfiguration.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotSimpleAnswerConfiguration.kt
@@ -18,6 +18,7 @@ package ai.tock.bot.admin.model
 
 import ai.tock.bot.admin.answer.AnswerConfigurationType
 import ai.tock.bot.admin.answer.SimpleAnswerConfiguration
+import java.util.Locale
 
 /**
  *
@@ -25,6 +26,6 @@ import ai.tock.bot.admin.answer.SimpleAnswerConfiguration
 data class BotSimpleAnswerConfiguration(val answers: List<BotSimpleAnswer>)
     : BotAnswerConfiguration(AnswerConfigurationType.simple) {
 
-    constructor(conf: SimpleAnswerConfiguration) : this(conf.answers.map { BotSimpleAnswer(it) })
+    constructor(conf: SimpleAnswerConfiguration, locale: Locale?) : this(conf.answers.map { BotSimpleAnswer(it, locale) })
 
 }
\ No newline at end of file
diff --git a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfiguration.kt b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfiguration.kt
index 2a3ccb476..034477db0 100644
--- a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfiguration.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfiguration.kt
@@ -29,10 +29,10 @@ import org.litote.kmongo.Id
 import org.litote.kmongo.newId
 import java.util.Locale
 
-internal fun List<AnswerConfiguration>.mapAnswers(): List<BotAnswerConfiguration> =
+internal fun List<AnswerConfiguration>.mapAnswers(locale: Locale?): List<BotAnswerConfiguration> =
     map {
         when (it) {
-            is SimpleAnswerConfiguration -> BotSimpleAnswerConfiguration(it)
+            is SimpleAnswerConfiguration -> BotSimpleAnswerConfiguration(it, locale)
             is ScriptAnswerConfiguration -> BotScriptAnswerConfiguration(it)
             is BuiltInAnswerConfiguration -> BotBuiltinAnswerConfiguration(it)
             else -> error("unsupported conf $it")
@@ -73,7 +73,7 @@ data class BotStoryDefinitionConfiguration(
         story.intent,
         story.currentType,
         story.namespace,
-        story.answers.mapAnswers(),
+        story.answers.mapAnswers(story.userSentenceLocale),
         story.mandatoryEntities.map { BotStoryDefinitionConfigurationMandatoryEntity(story, it) },
         story.steps.map { BotStoryDefinitionConfigurationStep(story, it) },
         story.name,
@@ -84,7 +84,7 @@ data class BotStoryDefinitionConfiguration(
         story.configurationName,
         story.features,
         story.tags,
-        story.configuredAnswers.map { BotConfiguredAnswer(it) },
+        story.configuredAnswers.map { BotConfiguredAnswer(it, story.userSentenceLocale) },
         story.configuredSteps.mapSteps(story),
         story._id
     )
diff --git a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationMandatoryEntity.kt b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationMandatoryEntity.kt
index 924180a6c..5fe7bf85a 100644
--- a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationMandatoryEntity.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationMandatoryEntity.kt
@@ -63,7 +63,7 @@ data class BotStoryDefinitionConfigurationMandatoryEntity(
             e.role,
             e.entityType,
             e.intent,
-            e.answers.mapAnswers(),
+            e.answers.mapAnswers(story.userSentenceLocale),
             e.currentType,
             story.category
         )
diff --git a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationStep.kt b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationStep.kt
index a8e348dd9..cd415f748 100644
--- a/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationStep.kt
+++ b/bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationStep.kt
@@ -83,7 +83,7 @@ data class BotStoryDefinitionConfigurationStep(
             e.name.takeUnless { it.isBlank() } ?: "${e.intent?.name}_${e.level}",
             e.intent,
             e.targetIntent,
-            e.answers.mapAnswers(),
+            e.answers.mapAnswers(story.userSentenceLocale),
             e.currentType,
             story.category,
             (e.userSentenceLabel
diff --git a/bot/admin/server/src/test/kotlin/I18nCsvCodecTest.kt b/bot/admin/server/src/test/kotlin/I18nCsvCodecTest.kt
index 2bb1df659..5da2d2c6b 100644
--- a/bot/admin/server/src/test/kotlin/I18nCsvCodecTest.kt
+++ b/bot/admin/server/src/test/kotlin/I18nCsvCodecTest.kt
@@ -20,7 +20,6 @@ import com.github.salomonbrys.kodein.Kodein
 import com.github.salomonbrys.kodein.KodeinInjector
 import com.github.salomonbrys.kodein.bind
 import com.github.salomonbrys.kodein.provider
-import ai.tock.shared.defaultNamespace
 import ai.tock.shared.tockInternalInjector
 import ai.tock.translator.I18nDAO
 import ai.tock.translator.I18nLabel
@@ -55,10 +54,15 @@ class I18nCsvCodecTest {
             })
         }
 
-        val id = "departuresarrivals_départs_suivants"
+        val namespace = "mynamespace"
+        val category = "departuresarrivals"
+        val id = namespace + "_" + category + "_départs_suivants"
         val export = """Label;Category;Language;Interface;Id;Validated;Connector;Alternatives
-Départs suivants;departuresarrivals;fr;textChat;departuresarrivals_départs_suivants;false;
-Départs suivants;departuresarrivals;fr;voiceAssistant;departuresarrivals_départs_suivants;true;"""
+Départs suivants;departuresarrivals;fr;textChat;mynamespace_departuresarrivals_départs_suivants;false;
+Départs suivants;departuresarrivals;fr;voiceAssistant;mynamespace_departuresarrivals_départs_suivants;true;"""
+        val exportWithNs = """Label;Namespace;Category;Language;Interface;Id;Validated;Connector;Alternatives
+Départs suivants;mynamespace;departuresarrivals;fr;textChat;mynamespace_departuresarrivals_départs_suivants;false;
+Départs suivants;mynamespace;departuresarrivals;fr;voiceAssistant;mynamespace_departuresarrivals_départs_suivants;true;"""
     }
 
 
@@ -67,8 +71,8 @@ Départs suivants;departuresarrivals;fr;voiceAssistant;departuresarrivals_dépar
         every { i18nDAO.getLabelById(id.toId()) } answers {
             I18nLabel(
                 id.toId(),
-                defaultNamespace,
-                "departuresarrivals",
+                namespace,
+                category,
                 LinkedHashSet(
                     listOf(
                         I18nLocalizedLabel(
@@ -99,5 +103,36 @@ Départs suivants;departuresarrivals;fr;voiceAssistant;departuresarrivals_dépar
 
     }
 
+    @Test
+    fun `importCsv_should_update_id_when_namespace_in_csv`() {
+        val targetNamespace = "other"
+        I18nCsvCodec.importCsv(targetNamespace, exportWithNs)
+
+        val slot = slot<I18nLabel>()
+        verify {
+            i18nDAO.save(capture(slot))
+        }
+
+        val importedLabel = slot.captured
+        assertEquals("Départs suivants", importedLabel.i18n.first { it.interfaceType == voiceAssistant }.label)
+        assertEquals(targetNamespace, importedLabel.namespace)
+        assertEquals(id.replaceFirst(namespace + "_", targetNamespace + "_"), importedLabel._id.toString())
+    }
+
+    @Test
+    fun `importCsv_should_not_update_id_when_no_namespace_in_csv`() {
+        val targetNamespace = "other"
+        I18nCsvCodec.importCsv(targetNamespace, export)
+
+        val slot = slot<I18nLabel>()
+        verify {
+            i18nDAO.save(capture(slot))
+        }
+
+        val importedLabel = slot.captured
+        assertEquals("Départs suivants", importedLabel.i18n.first { it.interfaceType == voiceAssistant }.label)
+        assertEquals(targetNamespace, importedLabel.namespace)
+        assertEquals(id, importedLabel._id.toString())
+    }
 
 }
\ No newline at end of file
diff --git a/bot/admin/web/src/app/bot/i18n/i18n-import.component.html b/bot/admin/web/src/app/bot/i18n/i18n-import.component.html
index 14ac286bd..22d3065ed 100644
--- a/bot/admin/web/src/app/bot/i18n/i18n-import.component.html
+++ b/bot/admin/web/src/app/bot/i18n/i18n-import.component.html
@@ -17,8 +17,11 @@
   <nb-icon icon="alert-triangle" status="danger"></nb-icon>&nbsp; Only .csv or .json files are allowed.
 </div>
 <div class="import-content">
+  <p>Select a JSON or CSV (with headers) file to import labels.</p>
+  <p>Only validated labels will be imported to current namespace.</p>
   <span class="import-label">File:&nbsp;</span>
   <input id="file" type="file" ng2FileSelect
+         [uploader]="uploader"
          (change)="onChange($event)"
          class="file-input"
          [ngClass]="!invalidSelectedFile?'valid-import':'invalid-import'"/>
diff --git a/bot/admin/web/src/app/bot/i18n/i18n-import.component.ts b/bot/admin/web/src/app/bot/i18n/i18n-import.component.ts
index fab7245c6..d7a00ca89 100644
--- a/bot/admin/web/src/app/bot/i18n/i18n-import.component.ts
+++ b/bot/admin/web/src/app/bot/i18n/i18n-import.component.ts
@@ -16,7 +16,7 @@
 
 import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
 import {NbButtonComponent, NbWindowRef} from '@nebular/theme';
-import {FileUploader} from 'ng2-file-upload';
+import {FileItem, FileUploader, ParsedResponseHeaders} from "ng2-file-upload";
 import {DialogService} from '../../core-nlp/dialog.service';
 
 @Component({
@@ -37,12 +37,18 @@ export class I18nImportComponent implements OnInit, AfterViewInit {
   }
 
   ngOnInit(): void {
+    const refresh = this.windowRef.config.context['refresh'];
     this.uploader = new FileUploader({
       removeAfterUpload: true,
-      allowedMimeType: ['text/csv', 'text/json'],
-      queueLimit: 1,
       isHTML5: true
     });
+    this.uploader.onCompleteItem =
+      (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
+        this.loading = false;
+        this.closeWindow();
+        this.dialog.notify(`Labels have been imported successfully.`, "Labels Imported", {duration: 3000, status: "success"});
+        refresh();
+      };
   }
 
   ngAfterViewInit(): void {
@@ -58,9 +64,6 @@ export class I18nImportComponent implements OnInit, AfterViewInit {
     if (!this.invalidSelectedFile && importFrom) {
       importFrom(this.getFileType(), this.uploader);
     }
-    this.loading = false;
-    this.closeWindow();
-    this.dialog.notify(`Labels have been imported successfully.`, "Labels Imported", {duration: 3000, status: "success"});
   }
 
   private getFileType() {
diff --git a/bot/admin/web/src/app/bot/i18n/i18n-label.component.html b/bot/admin/web/src/app/bot/i18n/i18n-label.component.html
index dce16c2a2..9e10a3740 100644
--- a/bot/admin/web/src/app/bot/i18n/i18n-label.component.html
+++ b/bot/admin/web/src/app/bot/i18n/i18n-label.component.html
@@ -21,7 +21,7 @@
     <div class="i18n-label__title">
       <div style="display: flex;">
         <span class="i18n-label__title__label"
-              nbTooltip="key:{{i._id}}">{{i.defaultLocalizedLabel().label.length === 0 ? i._id : i.defaultLocalizedLabel().label}}
+              nbTooltip="ns: {{i.namespace}}, key: {{i._id}}">{{i.defaultLocalizedLabel().label.length === 0 ? i._id : i.defaultLocalizedLabel().label}}
         </span>
         <button class="i18n-label__title__delete" *ngIf="deleteAllowed" nbButton  outline status="danger" size="small" (click)="deleteLabel(i)"
         nbTooltip="Delete Label" color="warn">
@@ -34,7 +34,7 @@
         <span *ngIf="i.defaultLocalizedLabel().label !== i.defaultLabel" class="i18n-label__title__original_label"
         nbTooltip="Original Label">{{i.defaultLabel}}</span>
 
-        
+
 
         <span class="i18n-label__title__usage">
           <span *ngIf="i.statCount">used {{ i.statCount }} times,</span>
diff --git a/bot/admin/web/src/app/bot/i18n/i18n.component.ts b/bot/admin/web/src/app/bot/i18n/i18n.component.ts
index 6f9256edb..c3aa231cf 100644
--- a/bot/admin/web/src/app/bot/i18n/i18n.component.ts
+++ b/bot/admin/web/src/app/bot/i18n/i18n.component.ts
@@ -229,7 +229,7 @@ export class I18nComponent extends I18nController implements OnInit {
     this.importWindow = this.windowService.open(
       I18nImportComponent,
       {
-        title: 'Import From',
+        title: 'Import Labels from File',
         context: {
           importFrom: (type: string, uploader: FileUploader) => this.upload(type, uploader),
           refresh: () => this.refresh()
diff --git a/bot/admin/web/src/app/bot/story/search-story.component.ts b/bot/admin/web/src/app/bot/story/search-story.component.ts
index 78751e1e5..9dc27fffb 100644
--- a/bot/admin/web/src/app/bot/story/search-story.component.ts
+++ b/bot/admin/web/src/app/bot/story/search-story.component.ts
@@ -21,12 +21,11 @@ import {NlpService} from "../../nlp-tabs/nlp.service";
 import {StateService} from "../../core-nlp/state.service";
 import {StoryDefinitionConfiguration, StoryDefinitionConfigurationSummary, StorySearchQuery} from "../model/story";
 import {Subscription} from "rxjs";
-import {DialogService} from "../../core-nlp/dialog.service";
 import {FileItem, FileUploader, ParsedResponseHeaders} from "ng2-file-upload";
-import {MatDialog} from "@angular/material/dialog";
 import {ConfirmDialogComponent} from "../../shared-nlp/confirm-dialog/confirm-dialog.component";
 import {CanDeactivate} from "@angular/router";
 import {LocationStrategy} from "@angular/common";
+import { NbToastrService, NbDialogService } from '@nebular/theme';
 
 interface TreeNode<T> {
   data: T;
@@ -66,8 +65,8 @@ export class SearchStoryComponent implements OnInit, OnDestroy {
   constructor(private nlp: NlpService,
               public state: StateService,
               private bot: BotService,
-              private dialog: DialogService,
-              private matDialog: MatDialog,
+              private dialogService: NbDialogService,
+              private toastrService: NbToastrService,
               private location: LocationStrategy,
               private backButtonHolder: BackButtonHolder) {
   }
@@ -98,28 +97,27 @@ export class SearchStoryComponent implements OnInit, OnDestroy {
       this.bot.exportStory(this.state.currentApplication.name, story.storyId)
         .subscribe(blob => {
           saveAs(blob, this.state.currentApplication.name + "_" + story.storyId + ".json");
-          this.dialog.notify(`Dump provided`, "Dump");
+          this.toastrService.show(`Dump provided`, "Dump", {duration: 3000, status: "success"});
         })
     }, 1);
   }
 
   deleteStory(story: StoryDefinitionConfigurationSummary) {
-    let dialogRef = this.dialog.open(
-      this.matDialog,
+    const dialogRef = this.dialogService.open(
       ConfirmDialogComponent,
       {
-        data: {
+        context: {
           title: `Remove the story '${story.name}'`,
           subtitle: "Are you sure?",
           action: "Remove"
         }
       });
-    dialogRef.afterClosed().subscribe(result => {
+    dialogRef.onClose.subscribe(result => {
       if (result === "remove") {
         this.bot.deleteStory(story._id)
           .subscribe(_ => {
             this.delete(story.storyId)
-            this.dialog.notify(`Story deleted`, "Delete")
+            this.toastrService.show(`Story deleted`, "Delete", {duration: 3000, status: "success"});
           });
       }
     });
@@ -207,7 +205,7 @@ export class SearchStoryComponent implements OnInit, OnDestroy {
       this.bot.exportStories(this.state.currentApplication.name)
         .subscribe(blob => {
           saveAs(blob, this.state.currentApplication.name + "_stories.json");
-          this.dialog.notify(`Dump provided`, "Dump");
+          this.toastrService.show(`Dump provided`, "Dump", {duration: 3000, status: "success"});
         })
     }, 1);
   }
@@ -216,7 +214,7 @@ export class SearchStoryComponent implements OnInit, OnDestroy {
     this.uploader = new FileUploader({removeAfterUpload: true});
     this.uploader.onCompleteItem =
       (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
-        this.dialog.notify(`Dump uploaded`, "Dump");
+        this.toastrService.show(`Dump uploaded`, "Dump", {duration: 3000, status: "success"});
         this.state.resetConfiguration();
       };
     this.displayUpload = true;
diff --git a/bot/orchestration/pom.xml b/bot/orchestration/pom.xml
index ebf7d6e0b..2eb2a9046 100644
--- a/bot/orchestration/pom.xml
+++ b/bot/orchestration/pom.xml
@@ -24,8 +24,8 @@
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>tock-bot-orchestration</artifactId>
-    <name>Tock Bot orchestration</name>
-    <description>Orchestration between bots</description>
+    <name>Tock Bot Orchestration</name>
+    <description>Orchestration between bots.</description>
 
     <dependencies>
         <dependency>
@@ -38,8 +38,17 @@
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter</artifactId>
-            <version>RELEASE</version>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/translator/core/src/main/kotlin/Translator.kt b/translator/core/src/main/kotlin/Translator.kt
index 790349826..7b4ec5811 100644
--- a/translator/core/src/main/kotlin/Translator.kt
+++ b/translator/core/src/main/kotlin/Translator.kt
@@ -130,9 +130,9 @@ object Translator {
 
     fun saveIfNotExist(value: I18nLabelValue): I18nLabel = saveIfNotExist(value, defaultLocale)
 
-    private fun saveIfNotExist(value: I18nLabelValue, defaultLocale: Locale): I18nLabel = getLabel(value) ?: {
+    fun saveIfNotExist(value: I18nLabelValue, locale: Locale?): I18nLabel = getLabel(value) ?: {
         val defaultLabelKey = value.defaultLabel.toString()
-        val defaultLabel = I18nLocalizedLabel(defaultLocale, defaultInterface, defaultLabelKey)
+        val defaultLabel = I18nLocalizedLabel(locale ?: defaultLocale, defaultInterface, defaultLabelKey)
         val label =
             I18nLabel(
                 value.key.toId(),
diff --git a/translator/core/src/test/kotlin/TranslatorTest.kt b/translator/core/src/test/kotlin/TranslatorTest.kt
index 8a7083f79..980c8be30 100644
--- a/translator/core/src/test/kotlin/TranslatorTest.kt
+++ b/translator/core/src/test/kotlin/TranslatorTest.kt
@@ -20,10 +20,14 @@ import ai.tock.shared.defaultLocale
 import ai.tock.shared.defaultNamespace
 import ai.tock.translator.UserInterfaceType.textChat
 import io.mockk.every
+import io.mockk.slot
 import io.mockk.verify
 import org.junit.jupiter.api.Test
 import org.litote.kmongo.toId
+import java.util.Locale
+import kotlin.collections.LinkedHashSet
 import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
 
 /**
  *
@@ -195,4 +199,66 @@ class TranslatorTest : AbstractTest() {
         val index = 1
         assertEquals("b", Translator.randomText(l, label, "contextId", index))
     }
+
+    @Test
+    fun `saveIfNotExist should use default locale when none provided`() {
+
+        val locale = Locale("fr")
+        assertNotEquals(locale, defaultLocale)
+
+        val localizedLabel = I18nLocalizedLabel(
+                locale,
+                defaultUserInterface,
+                "a",
+                listOf("b")
+        )
+        val label = I18nLabel(
+                "".toId(),
+                defaultNamespace,
+                "",
+                LinkedHashSet(listOf(localizedLabel))
+        )
+
+        every { i18nDAO.getLabelById(any()) }.returns(null)
+
+        Translator.saveIfNotExist(I18nLabelValue(label))
+
+        val slot = slot<I18nLabel>()
+        verify { i18nDAO.save(capture(slot)) }
+        val savedLabel = slot.captured
+
+        assertEquals(defaultLocale, savedLabel.defaultLocale)
+        assert(savedLabel.i18n.all { it.locale == defaultLocale })
+    }
+
+    @Test
+    fun `saveIfNotExist should use locale when provided`() {
+
+        val locale = Locale("fr")
+        assertNotEquals(locale, defaultLocale)
+
+        val localizedLabel = I18nLocalizedLabel(
+                locale,
+                defaultUserInterface,
+                "a",
+                listOf("b")
+        )
+        val label = I18nLabel(
+                "".toId(),
+                defaultNamespace,
+                "",
+                LinkedHashSet(listOf(localizedLabel))
+        )
+
+        every { i18nDAO.getLabelById(any()) }.returns(null)
+
+        Translator.saveIfNotExist(I18nLabelValue(label), locale)
+
+        val slot = slot<I18nLabel>()
+        verify { i18nDAO.save(capture(slot)) }
+        val savedLabel = slot.captured
+
+        assertEquals(locale, savedLabel.defaultLocale)
+        assert(savedLabel.i18n.all { it.locale == locale })
+    }
 }
\ No newline at end of file
['bot/admin/server/src/main/kotlin/model/BotConfiguredAnswer.kt', 'bot/admin/web/src/app/bot/i18n/i18n-import.component.ts', 'bot/admin/server/src/main/kotlin/model/BotSimpleAnswer.kt', 'translator/core/src/main/kotlin/Translator.kt', 'bot/admin/web/src/app/bot/story/search-story.component.ts', 'bot/orchestration/pom.xml', 'bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationMandatoryEntity.kt', 'bot/admin/web/src/app/bot/i18n/i18n-label.component.html', 'bot/admin/server/src/main/kotlin/I18nCsvCodec.kt', 'bot/admin/server/src/test/kotlin/I18nCsvCodecTest.kt', 'bot/admin/web/src/app/bot/i18n/i18n.component.ts', 'translator/core/src/test/kotlin/TranslatorTest.kt', 'bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfigurationStep.kt', 'bot/admin/server/src/main/kotlin/BotAdminVerticle.kt', 'bot/admin/web/src/app/bot/i18n/i18n-import.component.html', 'bot/admin/server/src/main/kotlin/model/BotStoryDefinitionConfiguration.kt', 'bot/admin/server/src/main/kotlin/model/BotSimpleAnswerConfiguration.kt']