Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/README.md
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/README.md	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/README.md	(revision 35454)
@@ -25,9 +25,12 @@
 Firstly, development-specific code needs to be changed.
 
-1. In `TranscribeModule.js` change the `queryUrl` property to point to the primary transcribe endpoint.
+1. In `TranscribeModule.js` rename the transcribe methods to stop the test data being returned.
 
     ```js
-    -- this.queryUrl = "//localhost:8383/gs3-koreromaori/transcribe/test";
-    ++ this.queryUrl = "//localhost:8383/gs3-koreromaori/transcribe";
+    -- async transcribeFiles(files) {
+    ++ async transcribeFilesTest(files) {
+    // And
+    -- async transcribeFilesActual(files) {
+    ++ async transcribeFiles(files) {
     ```
 
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/App.vue	(revision 35454)
@@ -14,10 +14,5 @@
         </div>
 
-        <div v-if="showInfo" class="paper">
-            A tool to transcribe audio recordings of spoken MÄori. Basic support for editing the transcriptions is provided,
-            and they can be downloaded in multiple formats.
-            <br />
-            The actual transcriptions are produced with gratitude by using <a href="https://koreromaori.io">koreromaori.io</a>'s service.
-        </div>
+        <div v-if="showInfo" class="paper" v-html="translations.get('Description')" />
     </div>
 
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioTimeBar.vue
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioTimeBar.vue	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/AudioTimeBar.vue	(revision 35454)
@@ -3,5 +3,5 @@
     <input type="range" class="slider-continuous" v-model.number="sliderValue" min="0" :max="audioLength" step="0.01" :disabled="isDisabled" />
 
-    <button type="button" class="btn-fab" @click="stepBack" :disabled="isDisabled">
+    <button type="button" class="btn-fab theme-flat" @click="stepBack" :disabled="isDisabled">
         <span class="material-icons mdi-s">arrow_back_ios_new</span>
     </button>
@@ -9,5 +9,5 @@
     <span>{{ stringSliderValue }}</span>
 
-    <button type="button" class="btn-fab" @click="stepForward" :disabled="isDisabled">
+    <button type="button" class="btn-fab theme-flat" @click="stepForward" :disabled="isDisabled">
         <span class="material-icons mdi-s">arrow_forward_ios</span>
     </button>
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/TranscriptionItemEditor.vue
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/TranscriptionItemEditor.vue	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/TranscriptionItemEditor.vue	(revision 35454)
@@ -20,4 +20,6 @@
     </div>
 
+    <word-timing-selector :surroundingWords="getSurroundingWords()" />
+
     <div class="editor-controls">
         <audio-time-bar v-model.number="currentPlaybackTime" :audio-length="audioLength" :isDisabled="playbackState.id != transcription.id" />
@@ -81,4 +83,5 @@
 import AudioPlayback from "../js/AudioPlaybackModule"
 import AudioTimeBar from "./AudioTimeBar.vue"
+import WordTimingSelector from "./WordTimingSelector.vue"
 import Util from "../js/Util";
 
@@ -114,5 +117,6 @@
     name: "TranscriptionItemEditor",
     components: {
-        AudioTimeBar
+        AudioTimeBar,
+        WordTimingSelector
     },
     props: {
@@ -146,4 +150,7 @@
         async playAudio(startTime) {
             await AudioPlayback.play(this.transcription.id, startTime);
+        },
+        getSurroundingWords() {
+            return this.words.slice(2, 7);
         },
         /**
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/WordTimingSelector.vue
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/WordTimingSelector.vue	(revision 35454)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/components/WordTimingSelector.vue	(revision 35454)
@@ -0,0 +1,127 @@
+<template>
+<div class="word-timing-selector-root">
+    <span>{{ minTime }}</span>
+
+    <div class="words-container" ref="wordsContainer">
+        <input type="range" class="slider-continuous" ref="slider" />
+
+        <div class="surround-word" v-for="word in mySurroundingWords" :key="word.id" :style="{ left: word.left, width: word.length }">
+            <span>{{ word.word }}</span>
+        </div>
+    </div>
+
+    <span>{{ maxTime }}</span>
+</div>
+</template>
+
+<style scoped lang="scss">
+.word-timing-selector-root {
+    display: flex;
+    align-items: center;
+    gap: 1em;
+}
+
+.words-container {
+    flex-grow: 0.5;
+    position: relative;
+    height: 2.5em;
+}
+
+.surround-word {
+    position: absolute;
+    text-align: center;
+
+    user-select: none;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+
+    overflow: hidden;
+    background-color: rgba(var(--bg-color-raw), 0.3);
+    border: 1px solid rgba(var(--bg-color-raw), 0.6);
+}
+</style>
+
+<script>
+import Util from "../js/Util"
+
+class Word {
+    constructor(word, startTime, endTime, left, length) {
+        this.id = Util.generateUuid();
+        this.word = word;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.left = `${left}px`;
+        this.length = `${length}px`;
+    }
+}
+
+export default {
+    name: "WordTimingSelector",
+    props: {
+        /** @type {Array<{ word: String, startTime: Number, endTime: Number }>} */
+        surroundingWords: Array,
+        word: { word: String, startTime: Number, endTime: Number },
+        upperBound: Number
+    },
+    data() {
+        return {
+            isMounted: false,
+            mySurroundingWords: []
+        }
+    },
+    emits: [ "update:word", "update:word" ],
+    computed: {
+        minTime() {
+            if (this.surroundingWords.length === 0) {
+                return 0;
+            }
+
+            return Util.formatSecondsTimeString(this.surroundingWords[0].startTime, false, 2);
+        },
+        maxTime() {
+            if (this.surroundingWords.length === 0) {
+                return 0;
+            }
+
+            return Util.formatSecondsTimeString(this.surroundingWords[this.surroundingWords.length - 1].endTime, false, 2);
+        }
+    },
+    watch: {
+        surroundingWords() {
+            this.scaleWords();
+        }
+    },
+    methods: {
+        scaleWords() {
+            if (!this.isMounted) {
+                return;
+            }
+
+            const myWords = [];
+            if (this.surroundingWords.length === 0) {
+                return myWords;
+            }
+
+            const audioLength = this.surroundingWords[this.surroundingWords.length - 1].endTime - this.surroundingWords[0].startTime;
+            const sliderLengthPx = this.$refs.slider.offsetWidth;
+
+            const scalingFactor = sliderLengthPx / audioLength;
+
+            const offset = this.surroundingWords[0].startTime * scalingFactor;
+
+            for (const word of this.surroundingWords) {
+                const left = (word.startTime * scalingFactor - offset);
+                const length = (word.endTime - word.startTime) * scalingFactor;
+
+                myWords.push(new Word(word.word, word.startTime, word.endTime, left, length));
+            }
+
+            this.mySurroundingWords = myWords;
+        }
+    },
+    mounted() {
+        this.isMounted = true;
+        this.scaleWords();
+    }
+}
+</script>
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/js/TranscribeModule.js
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/js/TranscribeModule.js	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/js/TranscribeModule.js	(revision 35454)
@@ -67,6 +67,10 @@
 export default class TranscribeService {
     constructor() {
+        /* eslint-disable quotes */
+        this.TEST_JSON = '{"file_name":"akl_mi_pk_0002.wav","log":"Took 0.6s to transcribe 8.7s audio file /tmp/transcribe-aw7ujpu8-clean.wav","metadata":[{"char":"k","prob":7.0E-6,"start_time":0.24},{"char":"o","prob":8.3E-5,"start_time":0.26},{"char":"t","prob":0.995578,"start_time":0.42},{"char":"a","prob":0.997089,"start_time":0.44},{"char":"h","prob":3.0E-6,"start_time":0.54},{"char":"i","prob":8.0E-6,"start_time":0.56},{"char":" ","prob":2.2E-5,"start_time":0.6},{"char":"a","prob":0.982987,"start_time":0.64},{"char":"n","prob":5.0E-6,"start_time":0.8},{"char":"Å","prob":1.82E-4,"start_time":0.84},{"char":" ","prob":0.996944,"start_time":1.08},{"char":"t","prob":1.23E-4,"start_time":1.14},{"char":"e","prob":6.3E-5,"start_time":1.16},{"char":" ","prob":0.587595,"start_time":1.28},{"char":"t","prob":4.91E-4,"start_time":1.34},{"char":"u","prob":0.010654,"start_time":1.36},{"char":"p","prob":5.9E-5,"start_time":1.52},{"char":"u","prob":1.26E-4,"start_time":1.54},{"char":"n","prob":0.0,"start_time":1.7},{"char":"a","prob":0.99997,"start_time":1.72},{"char":" ","prob":0.993724,"start_time":2.14},{"char":"o","prob":4.3E-5,"start_time":2.22},{"char":" ","prob":0.99696,"start_time":2.32},{"char":"t","prob":1.4E-5,"start_time":2.36},{"char":"e","prob":3.8E-5,"start_time":2.38},{"char":" ","prob":0.996606,"start_time":2.48},{"char":"t","prob":0.0,"start_time":2.52},{"char":"a","prob":0.994873,"start_time":2.54},{"char":"Å","prob":2.9E-5,"start_time":2.64},{"char":"a","prob":0.994639,"start_time":2.66},{"char":"t","prob":3.0E-6,"start_time":2.8},{"char":"a","prob":0.99995,"start_time":2.82},{"char":" ","prob":0.0,"start_time":2.9},{"char":"m","prob":0.999606,"start_time":2.98},{"char":"Ä","prob":0.010628,"start_time":3.0},{"char":"o","prob":0.996415,"start_time":3.2},{"char":"r","prob":0.0,"start_time":3.28},{"char":"i","prob":0.999698,"start_time":3.32},{"char":" ","prob":0.997927,"start_time":4.5},{"char":"k","prob":0.0,"start_time":4.58},{"char":"o","prob":3.2E-5,"start_time":4.6},{"char":" ","prob":0.956949,"start_time":4.7},{"char":"r","prob":2.5E-5,"start_time":4.74},{"char":"a","prob":0.997649,"start_time":4.76},{"char":"Å","prob":0.0,"start_time":4.9},{"char":"i","prob":1.63E-4,"start_time":4.92},{"char":"n","prob":1.3E-5,"start_time":5.28},{"char":"u","prob":4.0E-6,"start_time":5.3},{"char":"i","prob":0.99997,"start_time":5.42},{"char":" ","prob":0.994305,"start_time":5.78},{"char":"e","prob":3.1E-5,"start_time":5.84},{"char":" ","prob":0.966097,"start_time":6.04},{"char":"t","prob":2.0E-6,"start_time":6.1},{"char":"Å«","prob":5.4E-5,"start_time":6.12},{"char":" ","prob":0.837368,"start_time":6.34},{"char":"n","prob":0.0,"start_time":6.38},{"char":"e","prob":0.004238,"start_time":6.4},{"char":"i","prob":0.99966,"start_time":6.5},{"char":" ","prob":0.99728,"start_time":7.28},{"char":"o","prob":6.28E-4,"start_time":7.36},{"char":" ","prob":0.961049,"start_time":7.48},{"char":"p","prob":5.0E-6,"start_time":7.52},{"char":"a","prob":0.984707,"start_time":7.54},{"char":"p","prob":9.0E-6,"start_time":7.66},{"char":"a","prob":0.998754,"start_time":7.68},{"char":"t","prob":0.002356,"start_time":7.88},{"char":"Å«","prob":1.24E-4,"start_time":7.9},{"char":"Ä","prob":0.883958,"start_time":8.0},{"char":"n","prob":0.985871,"start_time":8.24},{"char":"u","prob":0.907369,"start_time":8.26},{"char":"k","prob":0.999045,"start_time":8.38},{"char":"u","prob":0.995632,"start_time":8.4}],"success":true,"transcription":"kotahi anÅ te tupuna o te tangata mÄori ko ranginui e tÅ« nei o papatÅ«Änuku"}';
+        /* eslint-enable quotes */
+
         /** @type {String} The URL to which query POST requests should be made. */
-        this.queryUrl = "//localhost:8383/gs3-koreromaori/transcribe/test";
+        this.queryUrl = "//localhost:8383/gs3-koreromaori/transcribe";
 
         /** @type {Number} The maximum number of files which can be transcribed in one request to the API. */
@@ -117,4 +121,23 @@
      */
     async transcribeFiles(files) {
+        const objects = [];
+
+        for (const file of files) {
+            const object = JSON.parse(this.TEST_JSON);
+            object.file_name = file.name;
+            objects.push(object);
+        }
+
+        return objects;
+    }
+
+    /**
+     * Performs a query to transcribe the given audio files.
+     *
+     * @param {FileList | File[]} files The files to upload.
+     * @returns {Promise<TranscriptionModel[]>} The transcribed audio file.
+     * @throws {TranscriptionError} When the transcription request fails to complete.
+     */
+    async transcribeFilesActual(files) {
         const that = this;
         const formData = new FormData();
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/main.js	(revision 35454)
@@ -162,5 +162,5 @@
 
                 const namespaceEndIndex = components[0].lastIndexOf(".");
-                translations.set(components[0].slice(namespaceEndIndex + 1), components[1]);
+                translations.set(components[0].slice(namespaceEndIndex + 1), components.slice(1).join("="));
             }
 
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/src/styles/_material.scss	(revision 35454)
@@ -73,9 +73,10 @@
     border: none;
     box-shadow: var(--primary-box-shadow-thin);
-
     border-radius: var(--border-radius);
     padding: 0.5em;
+
     cursor: pointer;
     font-size: 1rem;
+    font-weight: 400;
     text-decoration: none;
 
@@ -161,4 +162,5 @@
     font-style: italic;
     cursor: inherit;
+    user-select: none;
 }
 
@@ -448,5 +450,5 @@
 
     &:hover {
-        --bg-color: rgba(var(--primary-bg-color), 0.15);//#EEE;
+        --bg-color: rgba(var(--primary-bg-color), 0.15);
     }
 }
Index: /main/trunk/model-interfaces-dev/atea/korero-maori-asr/vue.config.js
===================================================================
--- /main/trunk/model-interfaces-dev/atea/korero-maori-asr/vue.config.js	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/korero-maori-asr/vue.config.js	(revision 35454)
@@ -12,5 +12,5 @@
             .plugin("html")
             .tap(args => {
-                args[0].title = "Korero Maori Transcription Interface";
+                args[0].title = "Korero MÄori Transcription Interface";
                 return args;
             })
Index: /main/trunk/model-interfaces-dev/atea/resources/interface_atea.properties
===================================================================
--- /main/trunk/model-interfaces-dev/atea/resources/interface_atea.properties	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/resources/interface_atea.properties	(revision 35454)
@@ -1,3 +1,4 @@
 atea.Title=Korero MÄori Transcription Interface
+atea.Description=A tool to transcribe audio recordings of spoken MÄori. Basic support for editing the transcriptions is provided, and they can be downloaded in multiple formats.<br />The actual transcriptions are produced with gratitude by using <a href='https://koreromaori.io'>koreromaori.io</a>'s service.
 atea.ErrorTryAgain=Something went wrong! Please try again
 
Index: /main/trunk/model-interfaces-dev/atea/resources/interface_atea_mi.properties
===================================================================
--- /main/trunk/model-interfaces-dev/atea/resources/interface_atea_mi.properties	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/resources/interface_atea_mi.properties	(revision 35454)
@@ -1,3 +1,4 @@
 atea.Title=Atanga Tuhituhi Korero MÄori
+atea.Description=...
 atea.ErrorTryAgain=Kua he tetahi mea! Tena koa ngana
 
Index: /main/trunk/model-interfaces-dev/atea/transform/pages/korero-maori-asr.xsl
===================================================================
--- /main/trunk/model-interfaces-dev/atea/transform/pages/korero-maori-asr.xsl	(revision 35453)
+++ /main/trunk/model-interfaces-dev/atea/transform/pages/korero-maori-asr.xsl	(revision 35454)
@@ -16,5 +16,7 @@
     <xsl:variable name="groupPath"><xsl:value-of select="/page/pageRequest/paramList/param[@name='group']/@value"/></xsl:variable>
     <!-- set page title -->
-    <xsl:template name="pageTitle">Korero Maori Transcription Interface</xsl:template>
+    <xsl:template name="pageTitle">
+        <xsl:value-of disable-output-escaping="yes" select="util:getInterfaceText('atea', /page/@lang, 'atea.Title')"/>
+    </xsl:template>
 
     <!-- set page breadcrumbs -->
