From f2fa90208daf4354603d42c745ea8c2940ec4435 Mon Sep 17 00:00:00 2001 From: Vincent Young Date: Thu, 5 Dec 2024 13:13:40 -0500 Subject: [PATCH 1/6] ci: upgraded to Go 1.23.4 --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- Dockerfile | 2 +- go.mod | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8208899..361ee49 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.23.3' + go-version: '1.23.4' - name: Install golint run: go install golang.org/x/lint/golint@latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cbb6f4a..aa01ecc 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: "1.23.3" + go-version: "1.23.4" - run: bash .cross_compile.sh diff --git a/Dockerfile b/Dockerfile index cd68528..851ee79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23.3 AS builder +FROM golang:1.23.4 AS builder WORKDIR /go/src/github.com/OwO-Network/DeepLX COPY . . RUN go get -d -v ./ diff --git a/go.mod b/go.mod index eb4bd6d..9ad3bcb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/OwO-Network/DeepLX -go 1.23.3 +go 1.23.4 require ( github.com/abadojack/whatlanggo v1.0.1 From 1c8beb8a87a1231ca7081355379fcf111a9b5a62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:38:43 -0500 Subject: [PATCH 2/6] chore(deps): bump golang.org/x/crypto from 0.27.0 to 0.31.0 (#166) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 9ad3bcb..67c1679 100644 --- a/go.mod +++ b/go.mod @@ -45,13 +45,13 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.25.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 785bcfc..df80407 100644 --- a/go.sum +++ b/go.sum @@ -116,22 +116,22 @@ go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= From 2d8b84e2c8031a0886edefefa96365231be8a4c1 Mon Sep 17 00:00:00 2001 From: Vincent Young Date: Sun, 22 Dec 2024 14:18:30 -0500 Subject: [PATCH 3/6] Update compose.yaml --- compose.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compose.yaml b/compose.yaml index 0991a06..eb0bd1b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,3 @@ -version: '3.8' - services: deeplx: image: ghcr.io/owo-network/deeplx:latest @@ -8,4 +6,4 @@ services: - "1188:1188" # environment: # - TOKEN=helloworld - # - DL_SESSION=xxxxxx \ No newline at end of file + # - DL_SESSION=xxxxxx From 4a77cbf30e47bf62b91a206c29ad0040c82c51d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:59:52 -0500 Subject: [PATCH 4/6] chore(deps): bump golang.org/x/net from 0.29.0 to 0.33.0 (#173) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.29.0 to 0.33.0. - [Commits](https://github.com/golang/net/compare/v0.29.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 67c1679..9c2f558 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index df80407..ee7cf6a 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From bbfb8087935fcf279e9861df5bb1999177020228 Mon Sep 17 00:00:00 2001 From: Vincent Yang Date: Mon, 20 Jan 2025 17:11:53 -0500 Subject: [PATCH 5/6] fix: handle line breaks #151 --- translate/translate.go | 255 ++++++++++++++++++++++++----------------- 1 file changed, 149 insertions(+), 106 deletions(-) diff --git a/translate/translate.go b/translate/translate.go index 94ef602..311a358 100644 --- a/translate/translate.go +++ b/translate/translate.go @@ -2,7 +2,7 @@ * @Author: Vincent Young * @Date: 2024-09-16 11:59:24 * @LastEditors: Vincent Yang - * @LastEditTime: 2024-12-03 11:23:23 + * @LastEditTime: 2025-01-20 17:09:59 * @FilePath: /DeepLX/translate/translate.go * @Telegram: https://t.me/missuo * @GitHub: https://github.com/missuo @@ -126,87 +126,79 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, }, nil } - // Split text first - splitResult, err := splitText(text, tagHandling == "html" || tagHandling == "xml", proxyURL, dlSession) - if err != nil { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: err.Error(), - }, nil - } + // Split text by newlines and store them for later reconstruction + textParts := strings.Split(text, "\n") + var translatedParts []string + var allAlternatives [][]string // Store alternatives for each part - // Get detected language if source language is auto - if sourceLang == "auto" || sourceLang == "" { - sourceLang = strings.ToUpper(whatlanggo.DetectLang(text).Iso6391()) - } - - // Prepare jobs from split result - var jobs []Job - chunks := splitResult.Get("result.texts.0.chunks").Array() - for idx, chunk := range chunks { - sentence := chunk.Get("sentences.0") - - // Handle context - contextBefore := []string{} - contextAfter := []string{} - if idx > 0 { - contextBefore = []string{chunks[idx-1].Get("sentences.0.text").String()} - } - if idx < len(chunks)-1 { - contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()} + for _, part := range textParts { + if strings.TrimSpace(part) == "" { + translatedParts = append(translatedParts, "") + allAlternatives = append(allAlternatives, []string{""}) + continue } - jobs = append(jobs, Job{ - Kind: "default", - PreferredNumBeams: 4, - RawEnContextBefore: contextBefore, - RawEnContextAfter: contextAfter, - Sentences: []Sentence{{ - Prefix: sentence.Get("prefix").String(), - Text: sentence.Get("text").String(), - ID: idx + 1, - }}, - }) - } + // Split text first + splitResult, err := splitText(part, tagHandling == "html" || tagHandling == "xml", proxyURL, dlSession) + if err != nil { + return DeepLXTranslationResult{ + Code: http.StatusServiceUnavailable, + Message: err.Error(), + }, nil + } - hasRegionalVariant := false - targetLangCode := targetLang - targetLangParts := strings.Split(targetLang, "-") - if len(targetLangParts) > 1 { - targetLangCode = targetLangParts[0] - hasRegionalVariant = true - } + // Get detected language if source language is auto + if sourceLang == "auto" || sourceLang == "" { + sourceLang = strings.ToUpper(whatlanggo.DetectLang(part).Iso6391()) + } - // Prepare translation request - id := getRandomNumber() + // Prepare jobs from split result + var jobs []Job + chunks := splitResult.Get("result.texts.0.chunks").Array() + for idx, chunk := range chunks { + sentence := chunk.Get("sentences.0") - postData := &PostData{ - Jsonrpc: "2.0", - Method: "LMT_handle_jobs", - ID: id, - Params: Params{ - CommonJobParams: CommonJobParams{ - Mode: "translate", - }, - Lang: Lang{ - SourceLangComputed: strings.ToUpper(sourceLang), - TargetLang: strings.ToUpper(targetLangCode), - }, - Jobs: jobs, - Priority: 1, - Timestamp: getTimeStamp(getICount(text)), - }, - } + // Handle context + contextBefore := []string{} + contextAfter := []string{} + if idx > 0 { + contextBefore = []string{chunks[idx-1].Get("sentences.0.text").String()} + } + if idx < len(chunks)-1 { + contextAfter = []string{chunks[idx+1].Get("sentences.0.text").String()} + } - if hasRegionalVariant { - postData = &PostData{ + jobs = append(jobs, Job{ + Kind: "default", + PreferredNumBeams: 4, + RawEnContextBefore: contextBefore, + RawEnContextAfter: contextAfter, + Sentences: []Sentence{{ + Prefix: sentence.Get("prefix").String(), + Text: sentence.Get("text").String(), + ID: idx + 1, + }}, + }) + } + + hasRegionalVariant := false + targetLangCode := targetLang + targetLangParts := strings.Split(targetLang, "-") + if len(targetLangParts) > 1 { + targetLangCode = targetLangParts[0] + hasRegionalVariant = true + } + + // Prepare translation request + id := getRandomNumber() + + postData := &PostData{ Jsonrpc: "2.0", Method: "LMT_handle_jobs", ID: id, Params: Params{ CommonJobParams: CommonJobParams{ - Mode: "translate", - RegionalVariant: map[bool]string{true: targetLang, false: ""}[hasRegionalVariant], + Mode: "translate", }, Lang: Lang{ SourceLangComputed: strings.ToUpper(sourceLang), @@ -214,60 +206,111 @@ func TranslateByDeepLX(sourceLang, targetLang, text string, tagHandling string, }, Jobs: jobs, Priority: 1, - Timestamp: getTimeStamp(getICount(text)), + Timestamp: getTimeStamp(getICount(part)), }, } - } - // Make translation request - result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession) - if err != nil { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: err.Error(), - }, nil - } + if hasRegionalVariant { + postData = &PostData{ + Jsonrpc: "2.0", + Method: "LMT_handle_jobs", + ID: id, + Params: Params{ + CommonJobParams: CommonJobParams{ + Mode: "translate", + RegionalVariant: map[bool]string{true: targetLang, false: ""}[hasRegionalVariant], + }, + Lang: Lang{ + SourceLangComputed: strings.ToUpper(sourceLang), + TargetLang: strings.ToUpper(targetLangCode), + }, + Jobs: jobs, + Priority: 1, + Timestamp: getTimeStamp(getICount(part)), + }, + } + } - // Process translation results - var alternatives []string - var translatedText string + // Make translation request + result, err := makeRequest(postData, "LMT_handle_jobs", proxyURL, dlSession) + if err != nil { + return DeepLXTranslationResult{ + Code: http.StatusServiceUnavailable, + Message: err.Error(), + }, nil + } - translations := result.Get("result.translations").Array() - if len(translations) > 0 { - // Get alternatives - numBeams := len(translations[0].Get("beams").Array()) - for i := 0; i < numBeams; i++ { - var altText string + // Process translation results + var partTranslation string + var partAlternatives []string + + translations := result.Get("result.translations").Array() + if len(translations) > 0 { + // Process main translation for _, translation := range translations { - beams := translation.Get("beams").Array() - if i < len(beams) { - altText += beams[i].Get("sentences.0.text").String() + partTranslation += translation.Get("beams.0.sentences.0.text").String() + " " + } + partTranslation = strings.TrimSpace(partTranslation) + + // Process alternatives + numBeams := len(translations[0].Get("beams").Array()) + for i := 1; i < numBeams; i++ { // Start from 1 since 0 is the main translation + var altText string + for _, translation := range translations { + beams := translation.Get("beams").Array() + if i < len(beams) { + altText += beams[i].Get("sentences.0.text").String() + " " + } + } + if altText != "" { + partAlternatives = append(partAlternatives, strings.TrimSpace(altText)) } } - if altText != "" { - alternatives = append(alternatives, altText) - } } - // Get main translation - for _, translation := range translations { - translatedText += translation.Get("beams.0.sentences.0.text").String() + " " + if partTranslation == "" { + return DeepLXTranslationResult{ + Code: http.StatusServiceUnavailable, + Message: "Translation failed", + }, nil } - translatedText = strings.TrimSpace(translatedText) + + translatedParts = append(translatedParts, partTranslation) + allAlternatives = append(allAlternatives, partAlternatives) } - if translatedText == "" { - return DeepLXTranslationResult{ - Code: http.StatusServiceUnavailable, - Message: "Translation failed", - }, nil + // Join all translated parts with newlines + translatedText := strings.Join(translatedParts, "\n") + + // Combine alternatives with proper newline handling + var combinedAlternatives []string + maxAlts := 0 + for _, alts := range allAlternatives { + if len(alts) > maxAlts { + maxAlts = len(alts) + } + } + + // Create combined alternatives preserving line structure + for i := 0; i < maxAlts; i++ { + var altParts []string + for j, alts := range allAlternatives { + if i < len(alts) { + altParts = append(altParts, alts[i]) + } else if len(translatedParts[j]) == 0 { + altParts = append(altParts, "") // Keep empty lines + } else { + altParts = append(altParts, translatedParts[j]) // Use main translation if no alternative + } + } + combinedAlternatives = append(combinedAlternatives, strings.Join(altParts, "\n")) } return DeepLXTranslationResult{ Code: http.StatusOK, - ID: id, + ID: getRandomNumber(), // Using new ID for the complete translation Data: translatedText, - Alternatives: alternatives, + Alternatives: combinedAlternatives, SourceLang: sourceLang, TargetLang: targetLang, Method: map[bool]string{true: "Pro", false: "Free"}[dlSession != ""], From de9888ca5f760b165343bf8030bdb9b904a4ed4d Mon Sep 17 00:00:00 2001 From: Vincent Yang Date: Mon, 20 Jan 2025 17:18:47 -0500 Subject: [PATCH 6/6] Upgrade to Go 1.23.5 --- .github/workflows/ci.yaml | 2 +- .github/workflows/release.yaml | 2 +- Dockerfile | 2 +- go.mod | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 361ee49..bde39fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.23.4' + go-version: '1.23.5' - name: Install golint run: go install golang.org/x/lint/golint@latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index aa01ecc..6ec09b0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-go@v4 with: - go-version: "1.23.4" + go-version: "1.23.5" - run: bash .cross_compile.sh diff --git a/Dockerfile b/Dockerfile index 851ee79..75ac34b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23.4 AS builder +FROM golang:1.23.5 AS builder WORKDIR /go/src/github.com/OwO-Network/DeepLX COPY . . RUN go get -d -v ./ diff --git a/go.mod b/go.mod index 9c2f558..50e4b3c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/OwO-Network/DeepLX -go 1.23.4 +go 1.23.5 require ( github.com/abadojack/whatlanggo v1.0.1