From f340ab254da43170bae09bb4164bcc21a642c973 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 12:30:57 +0200 Subject: [PATCH 01/17] Update README.md to include goal of fork --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5133d4e..d50b9a6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This is an Alexa Skill that can be used to control your personal Audiobookshelf server. +This fork aims to implement a german configuration for the project, so it can be run by devices with german locale. + ## Requirements: 1. Publicly reachable Audiobookshelf server 2. Amazon Developer account (free) From 6842894e2cb69b035b683bebaa67cc3df0f6235f Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 12:34:38 +0200 Subject: [PATCH 02/17] Update skill.json to include locale de-DE --- skill-package/skill.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/skill-package/skill.json b/skill-package/skill.json index 4d02a03..e7a9010 100644 --- a/skill-package/skill.json +++ b/skill-package/skill.json @@ -46,6 +46,18 @@ "keywords": [], "name": "Audiobookshelf", "summary": "Play audiobooks from self-hosted Audiobookshelf server" + }, + "de-DE": { + "description": "Angepasster Skill zur wiedergabe von Dateien aus einer öffentlichen, privaten Audiobookshelf-Instanz", + "examplePhrases": [ + "Weiter", + "Ließ mir etwas vor", + "Ließ mir Der Herr der Ringe von J.R.R. Tolkien vor", + "Spule 30 Sekunden vor" + ], + "keywords": [], + "name": "Audiobookshelf", + "summary": "Gib Hörbücher vom selbst gehosteten Audiobookshelf-Server wieder." } }, "testingInstructions": "Sample Testing Instructions." From 2a8909561853e8b29122b10288f3fde7835426c8 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 12:46:51 +0200 Subject: [PATCH 03/17] Create de-DE.json --- .../interactionModels/custom/de-DE.json | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 skill-package/interactionModels/custom/de-DE.json diff --git a/skill-package/interactionModels/custom/de-DE.json b/skill-package/interactionModels/custom/de-DE.json new file mode 100644 index 0000000..6d8f710 --- /dev/null +++ b/skill-package/interactionModels/custom/de-DE.json @@ -0,0 +1,253 @@ +{ + "interactionModel": { + "dialog": { + "intents": [ + { + "slots": [ + { + "name": "time", + "elicitationRequired": true, + "type": "AMAZON.DURATION", + "confirmationRequired": false, + "prompts": { + "elicitation": "Elicit.Slot.454140924019.1491668277531" + } + } + ], + "name": "GoBackXTimeIntent", + "confirmationRequired": false, + "prompts": {} + }, + { + "slots": [ + { + "name": "time", + "elicitationRequired": true, + "type": "AMAZON.DURATION", + "confirmationRequired": false, + "prompts": { + "elicitation": "Elicit.Slot.1425758106737.86337652976" + } + } + ], + "name": "GoForwardXTimeIntent", + "confirmationRequired": false, + "prompts": {} + }, + { + "slots": [ + { + "name": "title", + "elicitationRequired": false, + "type": "AMAZON.Book", + "confirmationRequired": false, + "prompts": {} + }, + { + "name": "author", + "elicitationRequired": false, + "type": "AMAZON.Author", + "confirmationRequired": false, + "prompts": {} + } + ], + "name": "PlayBookIntent", + "confirmationRequired": false, + "prompts": {} + }, + { + "slots": [ + { + "name": "chapterNumber", + "elicitationRequired": true, + "type": "AMAZON.NUMBER", + "confirmationRequired": false, + "prompts": { + "elicitation": "Elicit.Slot.464967230894.1040650485539" + } + } + ], + "name": "GoToChapterX", + "confirmationRequired": false, + "prompts": {} + } + ], + "delegationStrategy": "ALWAYS" + }, + "languageModel": { + "intents": [ + { + "name": "AMAZON.CancelIntent", + "samples": [ + "Verlassen", + "Abbrechen" + ] + }, + { + "name": "AMAZON.HelpIntent", + "samples": [] + }, + { + "name": "AMAZON.StopIntent", + "samples": [] + }, + { + "slots": [], + "name": "HelloWorldIntent", + "samples": [ + "Hallo", + "Test" + ] + }, + { + "name": "AMAZON.NavigateHomeIntent", + "samples": [] + }, + { + "name": "AMAZON.FallbackIntent", + "samples": [] + }, + { + "slots": [], + "name": "PlayLastIntent", + "samples": [ + "Spiel das letzte Buch", + "weiter", + "Ließ mir das letzte Buch vor", + "Ließ mir was vor" + ] + }, + { + "name": "AMAZON.PauseIntent", + "samples": [] + }, + { + "name": "AMAZON.ResumeIntent", + "samples": [] + }, + { + "name": "AMAZON.PreviousIntent", + "samples": [] + }, + { + "slots": [ + { + "name": "time", + "type": "AMAZON.DURATION", + "samples": [ + "Spring {time} zurück", + "Spule {time} zurück", + "Gehe {time} zurück", + "{time} zurückspulen", + "{time} zurückgehen", + "{time} zurückspringen" + ] + } + ], + "name": "GoBackXTimeIntent", + "samples": [ + "Spring {time} zurück", + "Spule {time} zurück", + "Gehe {time} zurück", + "{time} zurückspulen", + "{time} zurückgehen", + "{time} zurückspringen" + ] + }, + { + "slots": [ + { + "name": "time", + "type": "AMAZON.DURATION", + "samples": [ + "Springe {time} vor" + ] + } + ], + "name": "GoForwardXTimeIntent", + "samples": [ + "Springe {time} vor", + "Überspring {time}", + "Spul {time} vor", + "Spring {time} nach vorne", + "Gehe {time} vor", + "{time} vorwärts", + "{time} überspringen", + "{time} vorspulen" + ] + }, + { + "slots": [ + { + "name": "title", + "type": "AMAZON.Book" + }, + { + "name": "author", + "type": "AMAZON.Author" + } + ], + "name": "PlayBookIntent", + "samples": [ + "gib {title} von {author} wieder", + "ließ mir {title} vor", + "ließ mir {title} von {author} vor", + "{title}", + "{title} von {author}", + "{author} {title}" + ] + }, + { + "slots": [ + { + "name": "chapterNumber", + "type": "AMAZON.NUMBER" + } + ], + "name": "GoToChapterX", + "samples": [ + "Gehe zu Kapitel {chapterNumber}", + "spring zu Kapitel {chapterNumber}", + "Spule zu Kapitel {chapterNumber} zurück", + "Spule zu Kapitel {chapterNumber} vor" + ] + }, + { + "name": "AMAZON.NextIntent", + "samples": [] + } + ], + "types": [], + "invocationName": "privates vorlesen" + }, + "prompts": [ + { + "variations": [ + { + "type": "PlainText", + "value": "Du kannst zum beispiel sagen gehe 15 sekunden zurück." + } + ], + "id": "Elicit.Slot.454140924019.1491668277531" + }, + { + "variations": [ + { + "type": "PlainText", + "value": "Du kannst zum beispiel sagen gehe 15 sekunden vor." + } + ], + "id": "Elicit.Slot.1425758106737.86337652976" + }, + { + "variations": [ + { + "type": "PlainText", + "value": "Du kannst zum beispiel sagen gehe zu kapitel fünf." + } + ], + "id": "Elicit.Slot.464967230894.1040650485539" + } + ] + } +} From 75b4eccc85914769a18cef345672b9e81b621197 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 12:55:09 +0200 Subject: [PATCH 04/17] Update index.js - Manually editted all speakOutput to german --- lambda/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lambda/index.js b/lambda/index.js index d4a6447..02491b0 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -413,7 +413,7 @@ const LaunchRequestHandler = { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest'; }, handle(handlerInput) { - const speakOutput = 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.'; + const speakOutput = 'Wilkommen bei Audiobookshelf.'; console.log(" ~~~ LOGGED AT END OF LaunchRequestHandler ") @@ -571,7 +571,7 @@ const PlayAudioIntentHandler = { //speakOutput = 'Resuming...'; } else { - speakOutput = 'Playing ' + bookTitle + " by " + author; + speakOutput = bookTitle + " von " + author; } console.log("Playing: " + playUrl) @@ -747,7 +747,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat console.log("Title: " + bookTitle) console.log("Author: " + author) if (!bookTitle) { - let speakOutput = 'I did not understand the request. For example, try saying "Play audiobook title by author".'; + let speakOutput = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".'; console.log("Book and/or author slot undefined") return handlerInput.responseBuilder .speak(speakOutput) @@ -803,7 +803,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat }); if (results[0].book.length == 0) { console.log("No book of title '" + bookTitle + "' found") - const speakOutput = "No book of title '" + bookTitle + "' found. Please try again."; + const speakOutput = "Kein Buch mit dem Titel '" + bookTitle + "' gefunden. Versuch es nochmal."; return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) .reprompt(sanitizeForSSML(speakOutput)) @@ -870,7 +870,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat // sync localSessionAttributes to sessionAttributes updateLocalSessionAttributes(sessionAttributes) - let speakOutput = 'Playing ' + userPlaySession.displayTitle + ' by ' + userPlaySession.displayAuthor; + let speakOutput = userPlaySession.displayTitle + ' von ' + userPlaySession.displayAuthor; console.log("Playing: " + playUrl) const chapterTitle = getCurrentChapterByBookTime(currentTime, userPlaySession).title @@ -1142,7 +1142,7 @@ const PlayBookIntentHandler = { // require a book title (could later implement playing by author I suppose, maybe in another intent) if (!bookTitle) { - let speakOutput = 'I did not understand the request. For example, try saying "Play audiobook title by author".'; + let speakOutput = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".'; console.log("Book and/or author slot undefined") return handlerInput.responseBuilder .speak(speakOutput) @@ -1365,7 +1365,7 @@ const PlayBookIntentHandler = { } else { console.log("Could not find a playable book (" + rawTitle + " by " + rawAuthor + ")") - let speakOutput = "Could not find a playable book matching: " + rawTitle + " by " + rawAuthor + ". Please try again." + let speakOutput = "Ich habe kein entsprechendes Buch gefunden: " + rawTitle + " von " + rawAuthor + ". Versuch es nochmal." return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) .reprompt(sanitizeForSSML(speakOutput)) @@ -1428,7 +1428,7 @@ const PlayBookIntentHandler = { // sync localSessionAttributes to sessionAttributes updateLocalSessionAttributes(sessionAttributes) - let speakOutput = 'Playing ' + userPlaySession.displayTitle + ' by ' + userPlaySession.displayAuthor; + let speakOutput = userPlaySession.displayTitle + ' von ' + userPlaySession.displayAuthor; console.log("Playing: " + playUrl) @@ -2061,7 +2061,7 @@ const UnsupportedAudioIntentHandler = { ); }, async handle(handlerInput) { - const speakOutput = 'Sorry, I can\'t support that yet.'; + const speakOutput = 'Entschuldige, das kann ich noch nicht.'; return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) @@ -2075,7 +2075,7 @@ const HelpIntentHandler = { && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent'; }, handle(handlerInput) { - const speakOutput = 'You can say "play audio" to start playing your book! How can I help?'; + const speakOutput = 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?'; return handlerInput.responseBuilder .speak(speakOutput) @@ -2095,7 +2095,7 @@ const CancelAndStopIntentHandler = { || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent'); }, handle(handlerInput) { - const speakOutput = 'Goodbye!'; + const speakOutput = 'Bis zum nächsten mal!'; const sessionAttributes = handlerInput.attributesManager.getSessionAttributes() const userPlaySession = sessionAttributes.userPlaySession @@ -2663,7 +2663,7 @@ const FallbackIntentHandler = { && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent'; }, handle(handlerInput) { - const speakOutput = 'Sorry, I don\'t know about that. Please try again.'; + const speakOutput = 'Entschuldige, das weiß ich nicht. Versuch es nochmal.'; return handlerInput.responseBuilder .speak(speakOutput) @@ -2749,7 +2749,7 @@ const IntentReflectorHandler = { }, handle(handlerInput) { const intentName = Alexa.getIntentName(handlerInput.requestEnvelope); - const speakOutput = `You just triggered ${intentName}`; + const speakOutput = `Du hast gerade ${intentName} ausgelöst.`; return handlerInput.responseBuilder .speak(speakOutput) @@ -2767,7 +2767,7 @@ const ErrorHandler = { return true; }, handle(handlerInput, error) { - const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.'; + const speakOutput = 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.'; console.log(`~~~~ Error handled: ${JSON.stringify(error)}`); return handlerInput.responseBuilder @@ -2857,4 +2857,4 @@ exports.handler = Alexa.SkillBuilders.custom() }) ) */ - .lambda(); \ No newline at end of file + .lambda(); From ed8122f078af0806cba7a29487b445af759f60bc Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:05:25 +0200 Subject: [PATCH 05/17] Include localeconf as Variable from config.js --- lambda/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda/index.js b/lambda/index.js index 02491b0..320cdb7 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -21,7 +21,7 @@ let localSessionAttributes = { //const ddbAdapter = require('ask-sdk-dynamodb-persistence-adapter'); // const Util = require('./util.js'); -const { ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js'); +const { localeconf, ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js'); // const { off, title } = require('process'); const baseheaders = { From b327540e705dcc32b0ed88cafee1f558b2ea4d0d Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:06:13 +0200 Subject: [PATCH 06/17] Include localeconf Variable --- lambda/config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lambda/config.js b/lambda/config.js index 4762430..bfeb0ca 100644 --- a/lambda/config.js +++ b/lambda/config.js @@ -1,9 +1,10 @@ // config.js module.exports = { + localeconf: 'de-DE', // may also be en-US ABS_API_KEY: 'xxxxxx', SERVER_URL: 'https://abs.domain.tld', USER_AGENT: 'AlexaSkill' // OTHER OPTIONAL HEADERS // CFAccessClientId: 'xxxxxxxxx.access', // CFAccessClientSecret: 'xxxxxxxx' -}; \ No newline at end of file +}; From 44beee8cd2888017e9ddb7db9e55b8e5bd1bc4f2 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:22:25 +0200 Subject: [PATCH 07/17] Create de-DE.js --- lambda/locale/de-DE.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lambda/locale/de-DE.js diff --git a/lambda/locale/de-DE.js b/lambda/locale/de-DE.js new file mode 100644 index 0000000..fe5628b --- /dev/null +++ b/lambda/locale/de-DE.js @@ -0,0 +1,19 @@ +// locale/de-DE.js +module.exports = { + SpeakWelcome = 'Wilkommen bei Audiobookshelf.', + SpeakPlaying = 'Spiele', + SpeakBy = 'von', + SpeakTryAgain = 'Versuch es nochmal.', + SpeakPlaybackBookHandlerNoBookTitle = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', + SpeakPlaybackBookHandlerNoBookFoundP1 = 'Kein Buch mit dem Titel', + SpeakPlaybackBookHandlerNoBookFoundP2 = 'gefunden. Versuch es nochmal.', + SpeakPlayBookIntentHandlerNoBookTitle = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', + SpeakPlayBookIntentHandlerNoBookFound = "Kein entsprechendes Buch gefunden:", + SpeakUnsupportedAudioIntentHandler = 'Entschuldige, das kann ich noch nicht.', + SpeakHelpIntentHandler = 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?', + SpeakCancelAndStopIntentHandlerGoodbye = 'Bis zum nächsten mal!', + SpeakFallbackIntentHandler = 'Entschuldige, das weiß ich nicht. Versuch es nochmal.', + SpeakIntentReflectorHandlerP1 = 'Du hast gerade', + SpeakIntentReflectorHandlerP2 = 'ausgelöst', + SpeakErrorHandler = 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.' +}; From b2a46ccdfcd2ddb3c3ef4fa076df9cd9e13eba05 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:27:28 +0200 Subject: [PATCH 08/17] Create en-US.js --- lambda/locale/en-US.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lambda/locale/en-US.js diff --git a/lambda/locale/en-US.js b/lambda/locale/en-US.js new file mode 100644 index 0000000..ae82af0 --- /dev/null +++ b/lambda/locale/en-US.js @@ -0,0 +1,19 @@ +// locale/en-US.js +module.exports = { + SpeakWelcome = 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.', + SpeakPlaying = 'Playing', + SpeakBy = 'by', + SpeakTryAgain = 'Please try again.', + SpeakPlaybackBookHandlerNoBookTitle = 'I did not understand the request. For example, try saying "Play audiobook title by author".', + SpeakPlaybackBookHandlerNoBookFoundP1 = '"No book of title', + SpeakPlaybackBookHandlerNoBookFoundP2 = 'found. Please try again.', + SpeakPlayBookIntentHandlerNoBookTitle = 'I did not understand the request. For example, try saying "Play audiobook title by author".', + SpeakPlayBookIntentHandlerNoBookFound = "Could not find a playable book matching:", + SpeakUnsupportedAudioIntentHandler = 'Sorry, I can\'t support that yet.', + SpeakHelpIntentHandler = 'You can say "play audio" to start playing your book! How can I help?', + SpeakCancelAndStopIntentHandlerGoodbye = 'Goodbye!', + SpeakFallbackIntentHandler = 'Sorry, I don\'t know about that. Please try again.', + SpeakIntentReflectorHandlerP1 = 'You just triggered', + SpeakIntentReflectorHandlerP2 = '', + SpeakErrorHandler = 'Sorry, I had trouble doing what you asked. Please try again.' +}; From c91e33983f6f28af152f2c6e9b68580ca2e2166e Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:34:25 +0200 Subject: [PATCH 09/17] Include const definition of locale variables --- lambda/index.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lambda/index.js b/lambda/index.js index 320cdb7..6e1ecee 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -23,6 +23,24 @@ let localSessionAttributes = { const { localeconf, ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js'); // const { off, title } = require('process'); +const { + SpeakWelcome, + SpeakPlaying, + SpeakBy, + SpeakTryAgain, + SpeakPlaybackBookHandlerNoBookTitle, + SpeakPlaybackBookHandlerNoBookFoundP1, + SpeakPlaybackBookHandlerNoBookFoundP2, + SpeakPlayBookIntentHandlerNoBookTitle, + SpeakPlayBookIntentHandlerNoBookFound, + SpeakUnsupportedAudioIntentHandler, + SpeakHelpIntentHandler, + SpeakCancelAndStopIntentHandlerGoodbye, + SpeakFallbackIntentHandler, + SpeakIntentReflectorHandlerP1, + SpeakIntentReflectorHandlerP2, + SpeakErrorHandler +} = require('./locale/'+localeconf+'.js'); const baseheaders = { "Content-Type": 'application/json', From 5b5950b1b1e54cb3044996c0194341ab5625c60d Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 13:43:49 +0200 Subject: [PATCH 10/17] Implement using locale config --- lambda/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lambda/index.js b/lambda/index.js index 6e1ecee..7a08148 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -431,7 +431,7 @@ const LaunchRequestHandler = { return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest'; }, handle(handlerInput) { - const speakOutput = 'Wilkommen bei Audiobookshelf.'; + const speakOutput = SpeakWelcome; console.log(" ~~~ LOGGED AT END OF LaunchRequestHandler ") @@ -589,7 +589,7 @@ const PlayAudioIntentHandler = { //speakOutput = 'Resuming...'; } else { - speakOutput = bookTitle + " von " + author; + speakOutput = bookTitle + " " + SpeakBy + " " + author; } console.log("Playing: " + playUrl) @@ -765,7 +765,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat console.log("Title: " + bookTitle) console.log("Author: " + author) if (!bookTitle) { - let speakOutput = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".'; + let speakOutput = SpeakPlaybackBookHandlerNoBookTitle; console.log("Book and/or author slot undefined") return handlerInput.responseBuilder .speak(speakOutput) @@ -821,7 +821,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat }); if (results[0].book.length == 0) { console.log("No book of title '" + bookTitle + "' found") - const speakOutput = "Kein Buch mit dem Titel '" + bookTitle + "' gefunden. Versuch es nochmal."; + const speakOutput = SpeakPlaybackBookHandlerNoBookFoundP1 + bookTitle + SpeakPlaybackBookHandlerNoBookFoundP2; return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) .reprompt(sanitizeForSSML(speakOutput)) @@ -888,7 +888,7 @@ const PlaybackBookHandler = { // this handler is not currently used (has limitat // sync localSessionAttributes to sessionAttributes updateLocalSessionAttributes(sessionAttributes) - let speakOutput = userPlaySession.displayTitle + ' von ' + userPlaySession.displayAuthor; + let speakOutput = userPlaySession.displayTitle + ' ' + SpeakBy + ' ' + userPlaySession.displayAuthor; console.log("Playing: " + playUrl) const chapterTitle = getCurrentChapterByBookTime(currentTime, userPlaySession).title @@ -1160,7 +1160,7 @@ const PlayBookIntentHandler = { // require a book title (could later implement playing by author I suppose, maybe in another intent) if (!bookTitle) { - let speakOutput = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".'; + let speakOutput = SpeakPlayBookIntentHandlerNoBookTitle; console.log("Book and/or author slot undefined") return handlerInput.responseBuilder .speak(speakOutput) @@ -1377,13 +1377,13 @@ const PlayBookIntentHandler = { // if an author was found, could offer to play one of their other books instead? // Would probably need to forward it to another intent.. if (libraryItem) { - console.log("Found a book in the library!") + console.log("Found a book in the library!"); console.log("Title: " + libraryItem.media.metadata.title); console.log("Author: " + libraryItem.media.metadata.authorName); } else { console.log("Could not find a playable book (" + rawTitle + " by " + rawAuthor + ")") - let speakOutput = "Ich habe kein entsprechendes Buch gefunden: " + rawTitle + " von " + rawAuthor + ". Versuch es nochmal." + let speakOutput = SpeakPlayBookIntentHandlerNoBookFound + ' ' + rawTitle + " " + SpeakBy + " " + rawAuthor + ". " + SpeakTryAgain; return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) .reprompt(sanitizeForSSML(speakOutput)) @@ -1446,7 +1446,7 @@ const PlayBookIntentHandler = { // sync localSessionAttributes to sessionAttributes updateLocalSessionAttributes(sessionAttributes) - let speakOutput = userPlaySession.displayTitle + ' von ' + userPlaySession.displayAuthor; + let speakOutput = userPlaySession.displayTitle + ' ' + SpeakBy + ' ' + userPlaySession.displayAuthor; console.log("Playing: " + playUrl) @@ -2079,7 +2079,7 @@ const UnsupportedAudioIntentHandler = { ); }, async handle(handlerInput) { - const speakOutput = 'Entschuldige, das kann ich noch nicht.'; + const speakOutput = SpeakUnsupportedAudioIntentHandler; return handlerInput.responseBuilder .speak(sanitizeForSSML(speakOutput)) @@ -2093,7 +2093,7 @@ const HelpIntentHandler = { && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent'; }, handle(handlerInput) { - const speakOutput = 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?'; + const speakOutput = SpeakHelpIntentHandler; return handlerInput.responseBuilder .speak(speakOutput) @@ -2113,7 +2113,7 @@ const CancelAndStopIntentHandler = { || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent'); }, handle(handlerInput) { - const speakOutput = 'Bis zum nächsten mal!'; + const speakOutput = SpeakCancelAndStopIntentHandlerGoodbye; const sessionAttributes = handlerInput.attributesManager.getSessionAttributes() const userPlaySession = sessionAttributes.userPlaySession @@ -2681,7 +2681,7 @@ const FallbackIntentHandler = { && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent'; }, handle(handlerInput) { - const speakOutput = 'Entschuldige, das weiß ich nicht. Versuch es nochmal.'; + const speakOutput = SpeakFallbackIntentHandler; return handlerInput.responseBuilder .speak(speakOutput) @@ -2767,7 +2767,7 @@ const IntentReflectorHandler = { }, handle(handlerInput) { const intentName = Alexa.getIntentName(handlerInput.requestEnvelope); - const speakOutput = `Du hast gerade ${intentName} ausgelöst.`; + const speakOutput = SpeakIntentReflectorHandlerP1 + `${intentName}` + SpeakIntentReflectorHandlerP2; return handlerInput.responseBuilder .speak(speakOutput) @@ -2785,7 +2785,7 @@ const ErrorHandler = { return true; }, handle(handlerInput, error) { - const speakOutput = 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.'; + const speakOutput = SpeakErrorHandler; console.log(`~~~~ Error handled: ${JSON.stringify(error)}`); return handlerInput.responseBuilder From ac2dad0fc1ef3037163a3dd6b1024ad46f18a49d Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 15:23:04 +0200 Subject: [PATCH 11/17] Update en-US.js - Fix Stupid error --- lambda/locale/en-US.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lambda/locale/en-US.js b/lambda/locale/en-US.js index ae82af0..98adf07 100644 --- a/lambda/locale/en-US.js +++ b/lambda/locale/en-US.js @@ -1,19 +1,19 @@ // locale/en-US.js module.exports = { - SpeakWelcome = 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.', - SpeakPlaying = 'Playing', - SpeakBy = 'by', - SpeakTryAgain = 'Please try again.', - SpeakPlaybackBookHandlerNoBookTitle = 'I did not understand the request. For example, try saying "Play audiobook title by author".', - SpeakPlaybackBookHandlerNoBookFoundP1 = '"No book of title', - SpeakPlaybackBookHandlerNoBookFoundP2 = 'found. Please try again.', - SpeakPlayBookIntentHandlerNoBookTitle = 'I did not understand the request. For example, try saying "Play audiobook title by author".', - SpeakPlayBookIntentHandlerNoBookFound = "Could not find a playable book matching:", - SpeakUnsupportedAudioIntentHandler = 'Sorry, I can\'t support that yet.', - SpeakHelpIntentHandler = 'You can say "play audio" to start playing your book! How can I help?', - SpeakCancelAndStopIntentHandlerGoodbye = 'Goodbye!', - SpeakFallbackIntentHandler = 'Sorry, I don\'t know about that. Please try again.', - SpeakIntentReflectorHandlerP1 = 'You just triggered', - SpeakIntentReflectorHandlerP2 = '', - SpeakErrorHandler = 'Sorry, I had trouble doing what you asked. Please try again.' + SpeakWelcome: 'Welcome to Audiobookshelf, you can say "play audiobook" to start listening.', + SpeakPlaying: 'Playing', + SpeakBy: 'by', + SpeakTryAgain: 'Please try again.', + SpeakPlaybackBookHandlerNoBookTitle: 'I did not understand the request. For example, try saying "Play audiobook title by author".', + SpeakPlaybackBookHandlerNoBookFoundP1: '"No book of title', + SpeakPlaybackBookHandlerNoBookFoundP2: 'found. Please try again.', + SpeakPlayBookIntentHandlerNoBookTitle: 'I did not understand the request. For example, try saying "Play audiobook title by author".', + SpeakPlayBookIntentHandlerNoBookFound: "Could not find a playable book matching:", + SpeakUnsupportedAudioIntentHandler: 'Sorry, I can\'t support that yet.', + SpeakHelpIntentHandler: 'You can say "play audio" to start playing your book! How can I help?', + SpeakCancelAndStopIntentHandlerGoodbye: 'Goodbye!', + SpeakFallbackIntentHandler: 'Sorry, I don\'t know about that. Please try again.', + SpeakIntentReflectorHandlerP1: 'You just triggered', + SpeakIntentReflectorHandlerP2: '', + SpeakErrorHandler: 'Sorry, I had trouble doing what you asked. Please try again.' }; From 28c0818213e803cf81a20d423818c33036da7a3d Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 15:23:34 +0200 Subject: [PATCH 12/17] Update de-DE.js - Fix stupid Mistake --- lambda/locale/de-DE.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lambda/locale/de-DE.js b/lambda/locale/de-DE.js index fe5628b..903939e 100644 --- a/lambda/locale/de-DE.js +++ b/lambda/locale/de-DE.js @@ -1,19 +1,19 @@ -// locale/de-DE.js +// locale/de-de.js module.exports = { - SpeakWelcome = 'Wilkommen bei Audiobookshelf.', - SpeakPlaying = 'Spiele', - SpeakBy = 'von', - SpeakTryAgain = 'Versuch es nochmal.', - SpeakPlaybackBookHandlerNoBookTitle = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', - SpeakPlaybackBookHandlerNoBookFoundP1 = 'Kein Buch mit dem Titel', - SpeakPlaybackBookHandlerNoBookFoundP2 = 'gefunden. Versuch es nochmal.', - SpeakPlayBookIntentHandlerNoBookTitle = 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', - SpeakPlayBookIntentHandlerNoBookFound = "Kein entsprechendes Buch gefunden:", - SpeakUnsupportedAudioIntentHandler = 'Entschuldige, das kann ich noch nicht.', - SpeakHelpIntentHandler = 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?', - SpeakCancelAndStopIntentHandlerGoodbye = 'Bis zum nächsten mal!', - SpeakFallbackIntentHandler = 'Entschuldige, das weiß ich nicht. Versuch es nochmal.', - SpeakIntentReflectorHandlerP1 = 'Du hast gerade', - SpeakIntentReflectorHandlerP2 = 'ausgelöst', - SpeakErrorHandler = 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.' + SpeakWelcome: 'Wilkommen bei Audiobookshelf.', + SpeakPlaying: 'Spiele', + SpeakBy: 'von', + SpeakTryAgain: 'Versuch es nochmal.', + SpeakPlaybackBookHandlerNoBookTitle: 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', + SpeakPlaybackBookHandlerNoBookFoundP1: 'Kein Buch mit dem Titel', + SpeakPlaybackBookHandlerNoBookFoundP2: 'gefunden. Versuch es nochmal.', + SpeakPlayBookIntentHandlerNoBookTitle: 'Das habe ich nicht verstanden. Versuch es mit "Ließ mir Titel von Autor vor".', + SpeakPlayBookIntentHandlerNoBookFound: "Kein entsprechendes Buch gefunden:", + SpeakUnsupportedAudioIntentHandler: 'Entschuldige, das kann ich noch nicht.', + SpeakHelpIntentHandler: 'Du kannst sagen "Ließ mir etwas vor" - wie kann ich helfen?', + SpeakCancelAndStopIntentHandlerGoodbye: 'Bis zum nächsten mal!', + SpeakFallbackIntentHandler: 'Entschuldige, das weiß ich nicht. Versuch es nochmal.', + SpeakIntentReflectorHandlerP1: 'Du hast gerade', + SpeakIntentReflectorHandlerP2: 'ausgelöst', + SpeakErrorHandler: 'Entschuldige, ich hatte ein Problem dabei. Versuch es nochmal.' }; From 8c184ac94723ec72579465c03c6289256a795b7d Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 15:42:50 +0200 Subject: [PATCH 13/17] Update index.js - better readability/smal changes --- lambda/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lambda/index.js b/lambda/index.js index 7a08148..2a8ea87 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -23,6 +23,7 @@ let localSessionAttributes = { const { localeconf, ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js'); // const { off, title } = require('process'); +let localefile = './locale/' + localeconf + '.js' const { SpeakWelcome, SpeakPlaying, @@ -40,7 +41,7 @@ const { SpeakIntentReflectorHandlerP1, SpeakIntentReflectorHandlerP2, SpeakErrorHandler -} = require('./locale/'+localeconf+'.js'); +} = require(localefile); const baseheaders = { "Content-Type": 'application/json', From 371e1e7c7a78815ed12f1fa01b9401817fb0aa35 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 15:44:50 +0200 Subject: [PATCH 14/17] Update config.js - made en-US default locale --- lambda/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda/config.js b/lambda/config.js index bfeb0ca..92719d4 100644 --- a/lambda/config.js +++ b/lambda/config.js @@ -1,6 +1,6 @@ // config.js module.exports = { - localeconf: 'de-DE', // may also be en-US + localeconf: 'en-US', // may also be any other locale as defined in the folder locale and the package.json - use the locale your alexa is located in ABS_API_KEY: 'xxxxxx', SERVER_URL: 'https://abs.domain.tld', USER_AGENT: 'AlexaSkill' From db7799d7dc44354d3b0e35583c6dffedd49294b2 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 15:53:50 +0200 Subject: [PATCH 15/17] Update index.js - Small tweaks --- lambda/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda/index.js b/lambda/index.js index 2a8ea87..cddc580 100644 --- a/lambda/index.js +++ b/lambda/index.js @@ -23,7 +23,7 @@ let localSessionAttributes = { const { localeconf, ABS_API_KEY, SERVER_URL, CFAccessClientId, CFAccessClientSecret } = require('./config.js'); // const { off, title } = require('process'); -let localefile = './locale/' + localeconf + '.js' +const localefile = './locale/' + localeconf + '.js' const { SpeakWelcome, SpeakPlaying, @@ -590,7 +590,7 @@ const PlayAudioIntentHandler = { //speakOutput = 'Resuming...'; } else { - speakOutput = bookTitle + " " + SpeakBy + " " + author; + speakOutput = SpeakPlaying + bookTitle + " " + SpeakBy + " " + author; } console.log("Playing: " + playUrl) From 1b25d35c750ec264c9b9fc9f063146c677f6595f Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 16:11:15 +0200 Subject: [PATCH 16/17] Update README.md Changed Install instructions, so unaware users won't publish url/api-keys to a public github-repo. Also included instructions on how to add languages. --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d50b9a6..f962ee7 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,17 @@ This fork aims to implement a german configuration for the project, so it can be - performs an an API "search" that is built-in to ABS - if this fails, pulls all books from library and then performs a fuzzy search (effective, but may be resource intensive on large libraries) -## Installation: -1) Fork this repo -2) Edit the config.js file to include your **Audiobookshelf API key** and **server URL** (you can do this later in the 'Code' tab of Developer Console if using Alexa-hosted) +## Installation (Alexa hosted): +1) Follow the instructions here: https://developer.amazon.com/en-US/docs/alexa/hosted-skills/alexa-hosted-skills-git-import.html#import +2) Set your skill invocation name +3) Use the "Code" tab to edit config.js to include your **Audiobookshelf API key** and **server URL** +4) (Optional) Change locale +5) Save and deploy the skill +6) If using Alexa-hosted, go to the 'Test' tab of Developer Console, and enable skill testing for 'Development' + +## Installation (local hosted): +1) Fork this repo (and mark it private) +2) Edit the config.js file to include your **Audiobookshelf API key** and **server URL** 3) Follow the instructions here: https://developer.amazon.com/en-US/docs/alexa/hosted-skills/alexa-hosted-skills-git-import.html#import 4) Set your skill invocation name and build the skill 5) Save and deploy the skill @@ -63,3 +71,10 @@ This fork aims to implement a german configuration for the project, so it can be - [ ] Add other intents, such as: - [ ] "Start the book over" - [ ] "Go to chapter 12" + +## Language Contribution: +- Copy /skill-package/interactionModels/custom/en-US.json and edit the samples to reflect the locale you want to contribute. +- Edit /skill-package/skill.json to include the new locale under manifest/publishingInformation/locales +- Copy /lambda/locale/en-US.js and edit every string to reflect the new locale. + - If unsure about the usage of strings, open /lambda/index.js and search for ".speak" to find every time alexa is told to speak something. + - Maybe the new locale needs restructuring of the string construction - if you edit those, be sure to reflect the changes in all files under /lambda/locales/ From bb53876a156248fb3f5e41e61a3209fb3749ebe8 Mon Sep 17 00:00:00 2001 From: Ting3l Date: Tue, 3 Jun 2025 16:11:52 +0200 Subject: [PATCH 17/17] Update README.md Removed hint to german fork as preparation for pull request --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f962ee7..37a7a4f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ This is an Alexa Skill that can be used to control your personal Audiobookshelf server. -This fork aims to implement a german configuration for the project, so it can be run by devices with german locale. - ## Requirements: 1. Publicly reachable Audiobookshelf server 2. Amazon Developer account (free)