From 4df19f9acfd0428d33b00492932bb3726d746987 Mon Sep 17 00:00:00 2001 From: tsindt Date: Sun, 2 Aug 2026 23:25:13 +0200 Subject: [PATCH] =?UTF-8?q?=C3=9Cbersetzungen=20Server=20und=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/language/translations.api.ts | 26 ++ .../src/components/LanguagesTableView.tsx | 2 +- .../src/components/TranslationsTableView.tsx | 57 ++-- .../material-editor/src/data/translation.ts | 7 + .../src/pages/LanguagesPage.tsx | 6 +- .../controller/translationsController.go | 125 +++++++++ .../scpq-editor-server/models/translation.go | 8 + .../translationsRepository copy.go | 254 ++++++++++++++++++ .../scpq-editor-server/server/httpServer.go | 11 + .../services/translationsService copy.go | 59 ++++ 10 files changed, 526 insertions(+), 29 deletions(-) create mode 100644 buildtime/client/material-editor/src/api/language/translations.api.ts create mode 100644 buildtime/client/material-editor/src/data/translation.ts create mode 100644 buildtime/server/src/scpq-editor-server/controller/translationsController.go create mode 100644 buildtime/server/src/scpq-editor-server/models/translation.go create mode 100644 buildtime/server/src/scpq-editor-server/repositories/translationsRepository copy.go create mode 100644 buildtime/server/src/scpq-editor-server/services/translationsService copy.go diff --git a/buildtime/client/material-editor/src/api/language/translations.api.ts b/buildtime/client/material-editor/src/api/language/translations.api.ts new file mode 100644 index 000000000..df7d7cefa --- /dev/null +++ b/buildtime/client/material-editor/src/api/language/translations.api.ts @@ -0,0 +1,26 @@ +import ky from 'ky' +import type { Translation } from '../../data/translation' + +export const getTranslations = async (): Promise => { + return ky.get(`http://localhost:8088/translations`).json() +} + +export const getTranslationById = async (id: number): Promise => { + return ky.get(`http://localhost:8088/translation/${id}`).json() +} + +export const createTranslation = async (translation: Translation): Promise => { + return ky.post(`http://localhost:8088/translation`, { json: translation }).json() +} + +export const updateTranslation = async (translation: Translation): Promise => { + return ky.put(`http://localhost:8088/translation`, { json: translation }).json() +} + +export const deleteTranslation = async (id: number): Promise => { + return ky.delete(`http://localhost:8088/translation/${id}`).json() +} + +export const getTranslationsByLanguageId = async (language_id: number): Promise => { + return ky.get(`http://localhost:8088/translations/${language_id}`).json() +} diff --git a/buildtime/client/material-editor/src/components/LanguagesTableView.tsx b/buildtime/client/material-editor/src/components/LanguagesTableView.tsx index 9abc06d2c..d49b389b5 100644 --- a/buildtime/client/material-editor/src/components/LanguagesTableView.tsx +++ b/buildtime/client/material-editor/src/components/LanguagesTableView.tsx @@ -38,7 +38,7 @@ const LanguagesTableView = ({ languages, setSelectedRow }: { languages: Language return (
- + { - // const [languages, setLanguages] = useState([]); + const [translations, setTranslations] = React.useState([]); + const [language, setLanguage] = React.useState(null); + + React.useEffect(() => { + getTranslationsByLanguageId(selectedRow).then(setTranslations); + }, [selectedRow]); + + React.useEffect(() => { + getLanguageById(selectedRow).then(setLanguage); + }, [selectedRow]); + function CustomToolbar() { return ( - Here you see all your available languages for translations. + Translations for {language?.language}. }> @@ -38,11 +49,8 @@ const TranslationsTableView = ({ selectedRow }: { selectedRow: number }) => { return (
- {selectedRow} -
- ); -} - /*{ + { rowSpacingType="margin" initialState={{ sorting: { - sortModel: [{ field: 'language', sort: 'asc' }], + sortModel: [{ field: 'original', sort: 'asc' }], }, }} - rows={languages} + rows={translations} columns={[ { field: 'id', headerName: 'ID', width: 100 ,align: 'right', headerAlign: 'right', editable: false}, - { field: 'locale', headerName: 'Locale', width: 200, editable: true }, - { field: 'language', headerName: 'Language', width: 200 , editable: true}, + { field: 'original', headerName: 'Original', width: 200 , editable: true}, + { field: 'translated', headerName: 'Translated', width: 200 , editable: true}, ]} - slots={{ + slots={{ toolbar: CustomToolbar, }} processRowUpdate={(updatedRow, originalRow) => { - updateLanguage({id: updatedRow.id, locale: updatedRow.locale, language: updatedRow.language}); + updateTranslation({ + id: updatedRow.id, + language_id: updatedRow.language_id, + translated: updatedRow.translated, + original: updatedRow.original} + ) return updatedRow; }} getRowClassName={(params) => params.indexRelativeToCurrentPage % 2 === 0 ? 'even-row' : 'odd-row' } - onSelectionModelChange={(newSelection) => { - const selectedRowId = newSelection[0]; - const selectedRow = languages.find((row) => row.id === selectedRowId); - setSelectedRow(selectedRow ? selectedRow.id : 0); - }} - showToolbar - /> } + />
); -};*/ +}; export default TranslationsTableView; \ No newline at end of file diff --git a/buildtime/client/material-editor/src/data/translation.ts b/buildtime/client/material-editor/src/data/translation.ts new file mode 100644 index 000000000..ba089d916 --- /dev/null +++ b/buildtime/client/material-editor/src/data/translation.ts @@ -0,0 +1,7 @@ +export type Translation = { + id: number; + language_id: number; + original: string; + translated: string; +}; + diff --git a/buildtime/client/material-editor/src/pages/LanguagesPage.tsx b/buildtime/client/material-editor/src/pages/LanguagesPage.tsx index 1d537009c..5af920076 100644 --- a/buildtime/client/material-editor/src/pages/LanguagesPage.tsx +++ b/buildtime/client/material-editor/src/pages/LanguagesPage.tsx @@ -1,9 +1,8 @@ import React, { useEffect, useState } from 'react'; -import LanguagesTableView from '../components/LanguagesTableView'; -import TranslationsTableView from '../components/TranslationsTableView'; +import LanguagesTableView from '../components/LanguagesTableView'; +import TranslationsTableView from '../components/TranslationsTableView'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import Grid from '@mui/material/Grid'; import type { Language } from '../data/language'; import { getLanguages } from '../api/language/languages.api'; @@ -23,6 +22,7 @@ const LanguagesPage: React.FC = () => { sx={{ display: "flex", // Arrange children in a row flexDirection: "row", + width: "100vw", // Full width of the parent p: 2, // Padding around the flex container bgcolor: "#f5f5f5" }} diff --git a/buildtime/server/src/scpq-editor-server/controller/translationsController.go b/buildtime/server/src/scpq-editor-server/controller/translationsController.go new file mode 100644 index 000000000..fd755082e --- /dev/null +++ b/buildtime/server/src/scpq-editor-server/controller/translationsController.go @@ -0,0 +1,125 @@ +package controller + +import ( + "encoding/json" + "io" + "log" + "net/http" + "scpq-editor-server/models" + "scpq-editor-server/services" + "strconv" + + "github.com/gin-gonic/gin" +) + +type TranslationsController struct { + TranslationsService *services.TranslationsService +} + +func NewTranslationsController(TranslationsService *services.TranslationsService) *TranslationsController { + return &TranslationsController{ + TranslationsService: TranslationsService, + } +} + +func (lc TranslationsController) CreateTranslation(ctx *gin.Context) { + body, err := io.ReadAll(ctx.Request.Body) + if err != nil { + log.Println("Error while reading create Translation request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + var Translation models.Translation + err = json.Unmarshal(body, &Translation) + if err != nil { + log.Println("Error while unmarshaling create runner request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + response, responseErr := lc.TranslationsService.CreateTranslation(&Translation) + if responseErr != nil { + ctx.AbortWithStatusJSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} + +func (lc TranslationsController) UpdateTranslation(ctx *gin.Context) { + body, err := io.ReadAll(ctx.Request.Body) + if err != nil { + log.Println("Error while reading update Translation request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + var Translation models.Translation + err = json.Unmarshal(body, &Translation) + if err != nil { + log.Println("Error while unmarshaling update Translation request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + responseErr := lc.TranslationsService.UpdateTranslation(&Translation) + if responseErr != nil { + ctx.AbortWithStatusJSON(responseErr.Status, responseErr) + return + } + ctx.Status(http.StatusNoContent) +} + +func (lc TranslationsController) DeleteTranslation(ctx *gin.Context) { + TranslationId := ctx.Param("id") + lId, err := strconv.Atoi(TranslationId) + if err != nil { + log.Println("Error TranslationId not int", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + + responseErr := lc.TranslationsService.DeleteTranslation(lId) + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.Status(http.StatusNoContent) +} + +func (lc TranslationsController) GetTranslation(ctx *gin.Context) { + TranslationId := ctx.Param("id") + lId, err := strconv.Atoi(TranslationId) + if err != nil { + log.Println("Error TranslationId not int", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + response, responseErr := lc.TranslationsService.GetTranslation(lId) + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} + +func (lc TranslationsController) GetAllTranslations(ctx *gin.Context) { + response, responseErr := lc.TranslationsService.GetAllTranslations() + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} + +func (lc TranslationsController) GetAllTranslationsByLanguageId(ctx *gin.Context) { + languageId := ctx.Param("languageId") + lId, err := strconv.Atoi(languageId) + if err != nil { + log.Println("Error languageId not int", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + response, responseErr := lc.TranslationsService.GetAllTranslationsByLanguageId(lId) + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} diff --git a/buildtime/server/src/scpq-editor-server/models/translation.go b/buildtime/server/src/scpq-editor-server/models/translation.go new file mode 100644 index 000000000..46573a040 --- /dev/null +++ b/buildtime/server/src/scpq-editor-server/models/translation.go @@ -0,0 +1,8 @@ +package models + +type Translation struct { + ID int `json:"id"` + LanguageID int `json:"language_id"` + Original string `json:"original"` + Translated string `json:"translated"` +} diff --git a/buildtime/server/src/scpq-editor-server/repositories/translationsRepository copy.go b/buildtime/server/src/scpq-editor-server/repositories/translationsRepository copy.go new file mode 100644 index 000000000..77a1a405a --- /dev/null +++ b/buildtime/server/src/scpq-editor-server/repositories/translationsRepository copy.go @@ -0,0 +1,254 @@ +package repositories + +import ( + "database/sql" + "net/http" + "scpq-editor-server/models" + "strconv" +) + +type TranslationsRepository struct { + dbHandler *sql.DB +} + +func NewTranslationsRepository(dbHandler *sql.DB) *TranslationsRepository { + return &TranslationsRepository{ + dbHandler: dbHandler, + } +} + +func (rr TranslationsRepository) CreateTranslation(Translation *models.Translation) (*models.Translation, *models.ResponseError) { + query := `INSERT INTO Translations(language_id, original, translated ) VALUES($1,$2,$3) RETURNING id` + rows, err := rr.dbHandler.Query(query, Translation.LanguageID, Translation.Original, Translation.Translated) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + defer rows.Close() + var idstr string + for rows.Next() { + err := rows.Scan(&idstr) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + } + if rows.Err() != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + TranslationId, err := strconv.Atoi(idstr) + if err != nil { + return nil, &models.ResponseError{ + Message: "Translation id not int", + Status: http.StatusInternalServerError, + } + } + + return &models.Translation{ + ID: TranslationId, + LanguageID: Translation.LanguageID, + Original: Translation.Original, + Translated: Translation.Translated, + }, nil +} + +func (rr TranslationsRepository) UpdateTranslation(Translation *models.Translation) *models.ResponseError { + query := `UPDATE Translations SET language_id=$1, original=$2, translated=$3 WHERE id=$4` + res, err := rr.dbHandler.Exec(query, Translation.LanguageID, Translation.Original, Translation.Translated, Translation.ID) + if err != nil { + return &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + rowsAffeced, err := res.RowsAffected() + if err != nil { + return &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + if rowsAffeced == 0 { + return &models.ResponseError{ + Message: "No Translation found with the given id", + Status: http.StatusNotFound, + } + } + return nil +} +func (rr TranslationsRepository) DeleteTranslation(TranslationId int) *models.ResponseError { + query := `DELETE FROM Translations WHERE id=$1` + res, err := rr.dbHandler.Exec(query, TranslationId) + if err != nil { + return &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + rowsAffeced, err := res.RowsAffected() + if err != nil { + return &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + if rowsAffeced == 0 { + return &models.ResponseError{ + Message: "No Translation found with the given id", + Status: http.StatusNotFound, + } + } + return nil +} + +func (rr TranslationsRepository) GetTranslation(TranslationId int) (*models.Translation, *models.ResponseError) { + query := `SELECT id, language_id, original, translated FROM Translations WHERE id=$1` + rows, err := rr.dbHandler.Query(query, TranslationId) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + defer rows.Close() + var idstr, language_id_str, original, translated string + for rows.Next() { + err := rows.Scan(&idstr, &language_id_str, &original, &translated) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + } + if rows.Err() != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + languageId, err := strconv.Atoi(language_id_str) + if err != nil { + return nil, &models.ResponseError{ + Message: "Translation id not int", + Status: http.StatusInternalServerError, + } + } + + return &models.Translation{ + ID: TranslationId, + LanguageID: languageId, + Original: original, + Translated: translated, + }, nil +} + +func (rr TranslationsRepository) GetAllTranslations() ([]*models.Translation, *models.ResponseError) { + query := "SELECT id, language_id, original, translated FROM Translations" + rows, err := rr.dbHandler.Query(query) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + defer rows.Close() + Translations := make([]*models.Translation, 0) + var idstr, language_id_str, original, translated string + for rows.Next() { + err := rows.Scan(&idstr, &language_id_str, &original, &translated) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + id, err := strconv.Atoi(idstr) + if err != nil { + return nil, &models.ResponseError{ + Message: "Translation id not int", + Status: http.StatusInternalServerError, + } + } + languageId, err := strconv.Atoi(language_id_str) + if err != nil { + return nil, &models.ResponseError{ + Message: "Translation id not int", + Status: http.StatusInternalServerError, + } + } + Translation := &models.Translation{ + ID: id, + LanguageID: languageId, + Original: original, + Translated: translated, + } + Translations = append(Translations, Translation) + + } + if rows.Err() != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + return Translations, nil +} + +func (rr TranslationsRepository) GetAllTranslationsByLanguageID(languageId int) ([]*models.Translation, *models.ResponseError) { + query := "SELECT id, original, translated FROM Translations WHERE language_id=$1" + rows, err := rr.dbHandler.Query(query, languageId) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + + defer rows.Close() + Translations := make([]*models.Translation, 0) + var idstr, original, translated string + for rows.Next() { + err := rows.Scan(&idstr, &original, &translated) + if err != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + id, err := strconv.Atoi(idstr) + if err != nil { + return nil, &models.ResponseError{ + Message: "Translation id not int", + Status: http.StatusInternalServerError, + } + } + Translation := &models.Translation{ + ID: id, + LanguageID: languageId, + Original: original, + Translated: translated, + } + Translations = append(Translations, Translation) + + } + if rows.Err() != nil { + return nil, &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + return Translations, nil +} diff --git a/buildtime/server/src/scpq-editor-server/server/httpServer.go b/buildtime/server/src/scpq-editor-server/server/httpServer.go index 4ebe56d69..c2960b267 100644 --- a/buildtime/server/src/scpq-editor-server/server/httpServer.go +++ b/buildtime/server/src/scpq-editor-server/server/httpServer.go @@ -29,18 +29,21 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { propertygroupsRepository := repositories.NewPropertiesgroupsRepository(dbHandler) valuetypesRepository := repositories.NewValuetypesRepository(dbHandler) valuecomplexitiesRepository := repositories.NewValuecomplexitiesRepository(dbHandler) + translationsRepository := repositories.NewTranslationsRepository(dbHandler) // service initialisation: languagesService := services.NewLanguagesService(languagesRepository) propertygroupsService := services.NewPropertygroupsService(propertygroupsRepository) valuetypesService := services.NewValuetypesService(valuetypesRepository) valuecomplexitiesService := services.NewValuecomplexitiesService(valuecomplexitiesRepository) + translationsService := services.NewTranslationsService(translationsRepository) // controller initialisation: languageController := controller.NewLanguagesController(languagesService) propertygroupsController := controller.NewPropertygroupsController(propertygroupsService) valuetypesController := controller.NewValuetypesController(valuetypesService) valuecomplexitiesController := controller.NewValuecomplexitiesController(valuecomplexitiesService) + translationsController := controller.NewTranslationsController(translationsService) // Service API router := gin.Default() @@ -61,6 +64,14 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { router.GET("/propertygroup/:id", propertygroupsController.GetPropertygroup) router.GET("/propertygroups", propertygroupsController.GetAllPropertygroups) + // translation centric routing + router.POST("/translation", translationsController.CreateTranslation) + router.PUT("/translation", translationsController.UpdateTranslation) + router.DELETE("/translation/:id", translationsController.DeleteTranslation) + router.GET("/translation/:id", translationsController.GetTranslation) + router.GET("/translations", translationsController.GetAllTranslations) + router.GET("/translations/:languageId", translationsController.GetAllTranslationsByLanguageId) + // valuetype centric routing router.GET("/valuetype/:id", valuetypesController.GetValuetype) router.GET("/valuetypes", valuetypesController.GetAllValuetypes) diff --git a/buildtime/server/src/scpq-editor-server/services/translationsService copy.go b/buildtime/server/src/scpq-editor-server/services/translationsService copy.go new file mode 100644 index 000000000..0648fc8ba --- /dev/null +++ b/buildtime/server/src/scpq-editor-server/services/translationsService copy.go @@ -0,0 +1,59 @@ +package services + +import ( + "net/http" + "scpq-editor-server/models" + "scpq-editor-server/repositories" +) + +type TranslationsService struct { + translationsRepository *repositories.TranslationsRepository +} + +func NewTranslationsService(TranslationsRepository *repositories.TranslationsRepository) *TranslationsService { + return &TranslationsService{ + translationsRepository: TranslationsRepository, + } +} + +func (ls TranslationsService) CreateTranslation(translation *models.Translation) (*models.Translation, *models.ResponseError) { + responseErr := validateTranslation(translation) + if responseErr != nil { + return nil, responseErr + } + return ls.translationsRepository.CreateTranslation(translation) +} + +func (ls TranslationsService) UpdateTranslation(Translation *models.Translation) *models.ResponseError { + responseErr := validateTranslation(Translation) + if responseErr != nil { + return responseErr + } + return ls.translationsRepository.UpdateTranslation(Translation) +} + +func (ls TranslationsService) DeleteTranslation(TranslationId int) *models.ResponseError { + return ls.translationsRepository.DeleteTranslation(TranslationId) +} + +func (ls TranslationsService) GetTranslation(TranslationId int) (*models.Translation, *models.ResponseError) { + return ls.translationsRepository.GetTranslation(TranslationId) +} + +func (ls TranslationsService) GetAllTranslations() ([]*models.Translation, *models.ResponseError) { + return ls.translationsRepository.GetAllTranslations() +} + +func (ls TranslationsService) GetAllTranslationsByLanguageId(languageId int) ([]*models.Translation, *models.ResponseError) { + return ls.translationsRepository.GetAllTranslationsByLanguageID(languageId) +} + +func validateTranslation(Translation *models.Translation) *models.ResponseError { + if Translation.Original == "" { + return &models.ResponseError{ + Message: "Invalid Translation original", + Status: http.StatusBadRequest, + } + } + return nil +}