diff --git a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java index b15c974b06863734fc527fdc1108726c1db20424..5ef1cdcaddb9acbaac7f3b3f83c5f2d060758bc8 100644 --- a/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java +++ b/src/main/java/polish_community_group_11/polish_community/feed/controllers/FeedApisController.java @@ -50,15 +50,18 @@ public class FeedApisController { List<FeedImpl> posts = feedRepository.getAllPosts(); - // set isEditable flag for each post depedant on whether they are an admin or own the post - for (FeedImpl post : posts) { - log.info("Role ID: " + currentUser.getRoleId()); - boolean isSuperAdmin = currentUser != null && currentUser.getRoleId() == 1; - log.info("IsSuperAdmin: " + isSuperAdmin); - boolean isOwner = currentUser != null && post.getUserId() == currentUser.getId(); - log.info("IsPostOwner: " + isOwner); - post.setIsDeletable(isSuperAdmin || isOwner); - log.info("PostIsEditable: " + post.getIsDeletable()); + + if (currentUser != null){ + // set isEditable flag for each post depedant on whether they are an admin or own the post + for (FeedImpl post : posts) { + log.info("Role ID: " + currentUser.getRoleId()); + boolean isSuperAdmin = currentUser != null && currentUser.getRoleId() == 1; + log.info("IsSuperAdmin: " + isSuperAdmin); + boolean isOwner = currentUser != null && post.getUserId() == currentUser.getId(); + log.info("IsPostOwner: " + isOwner); + post.setIsDeletable(isSuperAdmin || isOwner); + log.info("PostIsEditable: " + post.getIsDeletable()); + } } return posts; diff --git a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java index c6b84198638ebbd06e9d2f7e05b1f7a40a4ffc0b..34029e3e1f9f3bc064cd7be86809ac353bb14b27 100644 --- a/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java +++ b/src/main/java/polish_community_group_11/polish_community/security/WebSecurityConfig.java @@ -33,7 +33,9 @@ public class WebSecurityConfig { private final RoleService roleService; private final String[] whiteListingPath = { - "/api/feed/**" +// "/event", +// "event/*" +// "/api/feed/**" , }; public WebSecurityConfig(UserService userService, RoleService roleService) { @@ -51,6 +53,8 @@ public class WebSecurityConfig { // require authentication for events only //update in future when more protected resources are available .authorizeHttpRequests((requests) -> requests +// .requestMatchers("/api/translations/**","/api/feed/**").permitAll() +// .requestMatchers().permitAll() .requestMatchers(whiteListingPath).authenticated() .anyRequest().permitAll() ) diff --git a/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java b/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java new file mode 100644 index 0000000000000000000000000000000000000000..878db0d444f76e602a9c5a01d69460892ad718de --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/controller/TranslationApisController.java @@ -0,0 +1,93 @@ +package polish_community_group_11.polish_community.translation.controller; + +import jakarta.annotation.PostConstruct; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.service.TranslationService; + +import java.util.List; + +@RestController +@RequestMapping("/api/translations") +public class TranslationApisController { + + private final TranslationService translationService; + + public TranslationApisController(TranslationService translationService) { + this.translationService = translationService; + } + + @GetMapping("/test") + public ResponseEntity<String> test() { + return ResponseEntity.ok("Translation API is working"); + } + + @PostConstruct + public void init() { + System.out.println("TranslationApisController initialized with mappings:"); + System.out.println("/api/translations"); + System.out.println("/api/translations/language/{language}"); + } + + // get all translations + @GetMapping + public ResponseEntity<List<Translation>> getAllTranslations() { + List<Translation> translations = translationService.getAllTranslations(); + return ResponseEntity.ok(translations); + } + + // for specific lang + @GetMapping("/language/{language}") + public ResponseEntity<List<Translation>> getTranslationsByLanguage(@PathVariable String language) { + List<Translation> translations = translationService.getTranslationsByLanguage(language); + return ResponseEntity.ok(translations); + } + + // for specific key and lang + @GetMapping("/{key}/language/{language}") + public ResponseEntity<Translation> getTranslationByKeyAndLanguage( + @PathVariable String key, + @PathVariable String language) { + Translation translation = translationService.getTranslationByKeyAndLanguage(key, language); + if (translation == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(translation); + } + + // adding new translation + @PostMapping + public ResponseEntity<String> addTranslation(@RequestBody Translation translation) { + translationService.addTranslation(translation); + return ResponseEntity.ok("Translation added successfully."); + } + + // update existing + @PutMapping("/{id}") + public ResponseEntity<String> updateTranslation(@PathVariable int id, @RequestBody Translation translation) { + translationService.updateTranslation(id, translation); + return ResponseEntity.ok("Translation updated successfully."); + } + + // deleting a translation + @DeleteMapping("/{id}") + public ResponseEntity<String> deleteTranslation(@PathVariable int id) { + translationService.deleteTranslation(id); + return ResponseEntity.ok("Translation deleted successfully."); + } + + // get all unique keys + @GetMapping("/keys") + public ResponseEntity<List<String>> getAllTranslationKeys() { + List<String> keys = translationService.getAllTranslationKeys(); + return ResponseEntity.ok(keys); + } + + // get all translations for a specific key across all langs + @GetMapping("/keys/{key}") + public ResponseEntity<List<Translation>> getTranslationsByKey(@PathVariable String key) { + List<Translation> translations = translationService.getTranslationsByKey(key); + return ResponseEntity.ok(translations); + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java b/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java new file mode 100644 index 0000000000000000000000000000000000000000..90b84c5c826b15fec1650fdcbc70369a6a92b359 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/model/Translation.java @@ -0,0 +1,93 @@ +package polish_community_group_11.polish_community.translation.model; + +public class Translation { + private int id; + private String key; + private String language; + private String value; + + public Translation () { + + } + + public Translation(int id, String key, String language, String value){ + this.id = id; + this.key = key; + this.language = language; + this.value = value; + + } + + public Translation(String key, String language, String value){ + this.key = key; + this.language = language; + this.value = value; + } + + // Getters and Setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + // some utility methods for debugging + @Override + public String toString() { + return "Translation{" + + "id=" + id + + ", key='" + key + '\'' + + ", language='" + language + '\'' + + ", value='" + value + '\'' + + '}'; + } + + // cheking if two translations equal each other + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Translation that = (Translation) o; + + if (id != that.id) return false; + if (!key.equals(that.key)) return false; + if (!language.equals(that.language)) return false; + return value.equals(that.value); + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + key.hashCode(); + result = 31 * result + language.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..378e164bb66cc13256fb1e4687bf5ba26d3080eb --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepository.java @@ -0,0 +1,32 @@ +package polish_community_group_11.polish_community.translation.repository; + +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +public interface TranslationRepository { + + + List<Translation> getAllTranslations(); + + + List<Translation> getTranslationsByLanguage(String language); + + + Translation getTranslationByKeyAndLanguage(String key, String language); + + + void addTranslation(Translation translation); + + + void updateTranslation(int id, Translation translation); + + + void deleteTranslation(int id); + + + List<String> getAllTranslationKeys(); + + + List<Translation> getTranslationsByKey(String key); +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..62bc181d910e15069afc33400e560536fcf69e0b --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/repository/TranslationRepositoryImpl.java @@ -0,0 +1,109 @@ +package polish_community_group_11.polish_community.translation.repository; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +@Repository +public class TranslationRepositoryImpl implements TranslationRepository { + private static final Logger logger = LoggerFactory.getLogger(TranslationRepositoryImpl.class); + + private final JdbcTemplate jdbcTemplate; + private final RowMapper<Translation> translationMapper; + + public TranslationRepositoryImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.translationMapper = (rs, rowNum) -> { + Translation translation = new Translation(); + translation.setId(rs.getInt("id")); + translation.setKey(rs.getString("translation_key")); + translation.setLanguage(rs.getString("language")); + translation.setValue(rs.getString("value")); + return translation; + }; + // Test database connection on startup + try { + jdbcTemplate.queryForObject("SELECT 1", Integer.class); + logger.info("Database connection successful"); + } catch (Exception e) { + logger.error("Database connection failed", e); + } + } + + @Override + public List<Translation> getTranslationsByLanguage(String language) { + try { + logger.info("Attempting to fetch translations for language: {}", language); + String sql = "SELECT * FROM translations WHERE language = ?"; + List<Translation> results = jdbcTemplate.query(sql, translationMapper, language); + logger.info("Found {} translations for language {}", results.size(), language); + return results; + } catch (Exception e) { + logger.error("Error fetching translations for language {}: {}", language, e.getMessage()); + throw e; + } + } + + // getting all translaions + @Override + public List<Translation> getAllTranslations() { + String sql = "SELECT * FROM translations"; + return jdbcTemplate.query(sql, translationMapper); + } + + // get translation by key and language + @Override + public Translation getTranslationByKeyAndLanguage(String key, String language) { + String sql = "SELECT * FROM translations WHERE translation_key = ? AND language = ?"; + return jdbcTemplate.queryForObject(sql, translationMapper, key, language); + } + + // Add a new translation + @Override + public void addTranslation(Translation translation) { + String sql = "INSERT INTO translations (translation_key, language, translation_value) VALUES (?, ?, ?)"; + jdbcTemplate.update(sql, + translation.getKey(), + translation.getLanguage(), + translation.getValue() + ); + } + + // update existing translation + @Override + public void updateTranslation(int id, Translation translation) { + String sql = "UPDATE translations SET translation_key = ?, language = ?, translation_value = ? WHERE id = ?"; + jdbcTemplate.update(sql, + translation.getKey(), + translation.getLanguage(), + translation.getValue(), + id + ); + } + + // delete a translation + @Override + public void deleteTranslation(int id) { + String sql = "DELETE FROM translations WHERE id = ?"; + jdbcTemplate.update(sql, id); + } + + // getting all unique keys for word list management + @Override + public List<String> getAllTranslationKeys() { + String sql = "SELECT DISTINCT translation_key FROM translations"; + return jdbcTemplate.query(sql, (rs, rowNum) -> rs.getString("translation_key")); + } + + // get translations for something across all languages + @Override + public List<Translation> getTranslationsByKey(String key) { + String sql = "SELECT * FROM translations WHERE translation_key = ?"; + return jdbcTemplate.query(sql, translationMapper, key); + } +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java new file mode 100644 index 0000000000000000000000000000000000000000..089f24dbdc3ebf37fe9bc8bfee9d0423b37fcad2 --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationService.java @@ -0,0 +1,34 @@ +package polish_community_group_11.polish_community.translation.service; + + + +import polish_community_group_11.polish_community.translation.model.Translation; + +import java.util.List; + +public interface TranslationService { + + + List<Translation> getAllTranslations(); + + + List<Translation> getTranslationsByLanguage(String language); + + + Translation getTranslationByKeyAndLanguage(String key, String language); + + + void addTranslation(Translation translation); + + + void updateTranslation(int id, Translation translation); + + + void deleteTranslation(int id); + + + List<String> getAllTranslationKeys(); + + + List<Translation> getTranslationsByKey(String key); +} diff --git a/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..82bba85308e0e4e116ff6c7f869b07d1f6f6394f --- /dev/null +++ b/src/main/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImpl.java @@ -0,0 +1,57 @@ +package polish_community_group_11.polish_community.translation.service; + +import org.springframework.stereotype.Service; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.repository.TranslationRepository; + +import java.util.List; + +@Service +public class TranslationServiceImpl implements TranslationService { + + private final TranslationRepository translationRepository; + + public TranslationServiceImpl(TranslationRepository translationRepository) { + this.translationRepository = translationRepository; + } + + @Override + public List<Translation> getAllTranslations() { + return translationRepository.getAllTranslations(); + } + + @Override + public List<Translation> getTranslationsByLanguage(String language) { + return translationRepository.getTranslationsByLanguage(language); + } + + @Override + public Translation getTranslationByKeyAndLanguage(String key, String language) { + return translationRepository.getTranslationByKeyAndLanguage(key, language); + } + + @Override + public void addTranslation(Translation translation) { + translationRepository.addTranslation(translation); + } + + @Override + public void updateTranslation(int id, Translation translation) { + translationRepository.updateTranslation(id, translation); + } + + @Override + public void deleteTranslation(int id) { + translationRepository.deleteTranslation(id); + } + + @Override + public List<String> getAllTranslationKeys() { + return translationRepository.getAllTranslationKeys(); + } + + @Override + public List<Translation> getTranslationsByKey(String key) { + return translationRepository.getTranslationsByKey(key); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e12924f4d009b519539abb5995545d2526ee5dcd..9e1b33138e2ec0bd4cc5cab1775b2e06c1daadc9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,7 +18,7 @@ server.error.whitelabel.enabled=false server.error.path=/error spring.jpa.hibernate.ddl-auto=none spring.sql.init.schema-locations=classpath:/schema.sql -spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_domains.sql +spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_domains.sql, classpath:/translations.sql #spring.jpa.hibernate.ddl-auto=none spring.jpa.defer-datasource-initialization=true diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 9e13838cf2db658a26107aac6d09575c4606f0dd..2371c4449bc48ab5ee94d057edc7beb68dc6f0a1 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -13,6 +13,7 @@ DROP TABLE IF EXISTS user_profile; DROP TABLE IF EXISTS user_roles; DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS roles; +DROP TABLE IF EXISTS translations; create table if not exists categories ( category_id INT AUTO_INCREMENT PRIMARY KEY, @@ -122,7 +123,7 @@ create table if not exists post_likes ( CREATE TABLE IF NOT EXISTS comment ( - id INT AUTO_INCREMENT PRIMARY KEY, + id INT AUTO_INCREMENT PRIMARY KEY, comment_content TEXT NOT NULL, created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, user_id INT NOT NULL, @@ -131,6 +132,15 @@ CREATE TABLE IF NOT EXISTS comment FOREIGN KEY (user_id) REFERENCES users (id) ) ENGINE = InnoDB; +create table if not exists translations ( + id int auto_increment primary key, + translation_key varchar(255) not null, + language varchar(10) not null, + value text not null +) engine = InnoDB; + +ALTER TABLE translations MODIFY value TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + -- create schema for event create table if not exists event( @@ -161,3 +171,4 @@ CREATE TABLE IF NOT EXISTS user_profile ( CREATE TRIGGER UMustHaveProfile AFTER INSERT ON users FOR EACH ROW INSERT INTO user_profile (user_id) VALUES (NEW.id); + diff --git a/src/main/resources/static/js/layout/layout.js b/src/main/resources/static/js/layout/layout.js new file mode 100644 index 0000000000000000000000000000000000000000..f4647f6766bd47d40c78a18f1af972f900fcf8f8 --- /dev/null +++ b/src/main/resources/static/js/layout/layout.js @@ -0,0 +1,54 @@ +let translations = []; +let currentLanguage = "en"; + +/** + * Fetch translations for the given language. + * @param {string} language - Language code to fetch translations for. + */ +async function fetchTranslations(language) { + try { + const response = await fetch(`/api/translations/language/${language}`); + if (!response.ok) { + throw new Error(`Failed to fetch translations: ${response.statusText}`); + } + translations = await response.json(); // This is now an array of translation objects + currentLanguage = language; + console.log("Translations updated:", translations); + updateUIWithTranslations(); + document.dispatchEvent(new Event("translations-updated")); + } catch (error) { + console.error("Error fetching translations:", error); + } +} + +function updateUIWithTranslations() { + const elementsToTranslate = document.querySelectorAll("[data-translate-key]"); + + elementsToTranslate.forEach(element => { + const translationKey = element.getAttribute("data-translate-key"); + // Find the translation object with matching key + const translation = translations.find(t => t.key === translationKey); + if (translation) { + element.textContent = translation.value; + } else { + console.warn(`No translation found for key: ${translationKey}`); + } + }); +} + +// fetch translations on page load +document.addEventListener("DOMContentLoaded", () => { + fetchTranslations(currentLanguage); + console.log("I have run"); + + // handle language selection + const languageSelector = document.querySelector(".languageSelector select"); + if (languageSelector) { + languageSelector.addEventListener("change", async (event) => { + const selectedLanguage = event.target.value; + if (selectedLanguage !== currentLanguage) { + await fetchTranslations(selectedLanguage); + } + }); + } +}); \ No newline at end of file diff --git a/src/main/resources/templates/home/home.html b/src/main/resources/templates/home/home.html index 54697729ca5270b7ec8bf5143524a05b020b24e8..2f8e70b1898dc70e031a5123a8535900a45e785f 100644 --- a/src/main/resources/templates/home/home.html +++ b/src/main/resources/templates/home/home.html @@ -10,60 +10,60 @@ </head> <section layout:fragment="content"> - <h1>Welcome to our Community</h1> - <p>Connect, Share, and Grow Together</p> + <h1 data-translate-key="home.header">Welcome to our Community</h1> + <p data-translate-key="home.smallheader">Connect, Share, and Grow Together</p> <section class="tiles"> <div class="tile" id="feedTile"> <a th:href="@{/feed}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/feed.png" class="tile-logo"><span class="tile-title">Feed</span> + <img src="/assets/navbarImages/feed.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.feed">Feed</span> </div> - <p class="tile-description">Stay updated with the latest posts</p> + <p class="tile-description" data-translate-key="home.latest_posts">Stay updated with the latest posts</p> </a> </div> <div class="tile" id="newsTile"> <a th:href="@{/news}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/news.png" class="tile-logo"><span class="tile-title">News</span> + <img src="/assets/navbarImages/news.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.news">News</span> </div> - <p class="tile-description">Discover the latest community news</p> + <p class="tile-description" data-translate-key="home.news">Discover the latest community news</p> </a> </div> <div class="tile" id="eventsTile"> <a th:href="@{/event}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/events.png" class="tile-logo"><span class="tile-title">Events</span> + <img src="/assets/navbarImages/events.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.events">Events</span> </div> - <p class="tile-description">Join our upcoming community events</p> + <p class="tile-description" data-translate-key="home.events">Join our upcoming community events</p> </a> </div> <div class="tile" id="infoTile"> <a th:href="@{/categories}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/info.png" class="tile-logo"><span class="tile-title">Info Database</span> + <img src="/assets/navbarImages/info.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.db_info">Info Database</span> </div> - <p class="tile-description">Access our community information database</p> + <p class="tile-description" data-translate-key="home.db_info">Access our community information database</p> </a> </div> <div class="tile" id="aboutTile"> <a th:href="@{/aboutUs}" class="tileLink"> <div class="topTile"> - <img src="/assets/navbarImages/about.png" class="tile-logo"><span class="tile-title">About Us</span> + <img src="/assets/navbarImages/about.png" class="tile-logo"><span class="tile-title" data-translate-key="navbar.about_us">About Us</span> </div> - <p class="tile-description">Learn about our mission and values</p> + <p class="tile-description" data-translate-key="home.about_us">Learn about our mission and values</p> </a> </div> </section> - <h1>Join Our Thriving Community Today!</h1> - <p>Connect with like-minded individuals, share your ideas, and be part of something amazing</p> - <button>Get Started</button> + <h1 data-translate-key="home.join_community">Join Our Thriving Community Today!</h1> + <p data-translate-key="home.tagline">Connect with like-minded individuals, share your ideas, and be part of something amazing</p> + <button data-translate-key="home.get_started">Get Started</button> </section> diff --git a/src/main/resources/templates/layout/layout.html b/src/main/resources/templates/layout/layout.html index 03d4db4d0ccfbe479a9ec72f65eb40cd510b0942..551413576f822a5295b69b111c05aa30248c5dd4 100644 --- a/src/main/resources/templates/layout/layout.html +++ b/src/main/resources/templates/layout/layout.html @@ -8,104 +8,112 @@ <title>Ludek Polonia Wajiska</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> - <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" + rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="/css/comments/comments.css"> <link rel="stylesheet" href="/css/layout/layout.css"> </head> <body> - <header class="header"> - <section class="sidebar"> - <nav class="sidebar"> - <div class="navLeft"> - <a th:href="@{/}" class="logoLink"> - <img src="/assets/navbarImages/logo.png" class="logoImage"><span class="navText">Ludek Polonia Wajiska</span> - </a> - </div> - - <div class="navMiddle"> - <a th:href="@{/}" class="navLink"> - <img src="/assets/navbarImages/home.png" class="navIcons"><span class="navText">Home</span> - </a> - <a th:href="@{/feed}" class="navLink"> - <img src="/assets/navbarImages/feed.png" class="navIcons"><span class="navText">Feed</span> - </a> - <a th:href="@{/news}" class="navLink"> - <img src="/assets/navbarImages/news.png" class="navIcons"><span class="navText">News</span> - </a> - <a th:href="@{/event}" class="navLink"> - <img src="/assets/navbarImages/events.png" class="navIcons"><span class="navText">Events</span> - </a> - <a th:href="@{/categories}" class="navLink"> - <img src="/assets/navbarImages/info.png" class="navIcons"><span class="navText">Info Database</span> - </a> - <a th:href="@{/aboutUs}" class="navLink"> - <img src="/assets/navbarImages/about.png" class="navIcons"><span class="navText">About Us</span> - </a> - <a th:href="@{/contactus}" class="navLink"> - <img src="/assets/navbarImages/contact.png" class="navIcons"><span class="navText">Contact Us</span> - </a> - </div> +<header class="header"> + <section class="sidebar"> + <nav class="sidebar"> + <div class="navLeft"> + <a th:href="@{/}" class="logoLink"> + <img src="/assets/navbarImages/logo.png" class="logoImage"><span class="navText">Ludek Polonia Wajiska</span> + </a> + </div> - <!-- Right Section: Language Selector and Profile --> - <div class="navRight"> - <!-- Language Selector --> - <div class="languageSelector"> - <img src="/assets/navbarImages/globe.png" class="navIcons"> - <select name="language"> - <option value="english">English</option> - <option value="polish">Polish</option> - </select> - </div> + <div class="navMiddle"> + <a th:href="@{/}" class="navLink"> + <img src="/assets/navbarImages/home.png" class="navIcons"><span class="navText" + data-translate-key="navbar.home">Home</span> + </a> + <a th:href="@{/feed}" class="navLink"> + <img src="/assets/navbarImages/feed.png" class="navIcons"><span class="navText" + data-translate-key="navbar.feed">Feed</span> + </a> + <a th:href="@{/news}" class="navLink"> + <img src="/assets/navbarImages/news.png" class="navIcons"><span class="navText" + data-translate-key="navbar.news">News</span> + </a> + <a th:href="@{/event}" class="navLink"> + <img src="/assets/navbarImages/events.png" class="navIcons"><span class="navText" + data-translate-key="navbar.events">Events</span> + </a> + <a th:href="@{/categories}" class="navLink"> + <img src="/assets/navbarImages/info.png" class="navIcons"><span class="navText" + data-translate-key="navbar.db_info">Info Database</span> + </a> + <a th:href="@{/aboutUs}" class="navLink"> + <img src="/assets/navbarImages/about.png" class="navIcons"><span class="navText" + data-translate-key="navbar.about_us">About Us</span> + </a> + <a th:href="@{/contactus}" class="navLink"> + <img src="/assets/navbarImages/contact.png" class="navIcons"><span class="navText" + data-translate-key="navbar.contact_us">Contact Us</span> + </a> + </div> - <!-- Profile --> - <a th:href="@{/profile}" class="navLink"> - <img src="/assets/navbarImages/profile.png" class="navIcons"><span class="navText">Profile</span> - </a> + <!-- Right Section: Language Selector and Profile --> + <div class="navRight"> + <!-- Language Selector --> + <div class="languageSelector"> + <img src="/assets/navbarImages/globe.png" class="navIcons"> + <select name="language"> + <option value="en">English</option> + <option value="pl">Polish</option> + </select> </div> - </nav> - </section> - </header> - <div class="mainBody"> + <!-- Profile --> + <a th:href="@{/profile}" class="navLink"> + <img src="/assets/navbarImages/profile.png" class="navIcons"><span class="navText" + data-translate-key="navbar.profile">Profile</span> + </a> + </div> + </nav> + </section> +</header> +<div class="mainBody"> + <main layout:fragment="content" class="content"></main> +</div> - <main layout:fragment="content" class="content"></main> +<footer class="footer"> + <div class="footer-section about"> + <h3 class="footerTitle">Ludek Polonia Wajiska</h3> + <p class="footerText" data-translate-key="footer.tagline">Connecting people, sharing ideas, and building a + better future together.</p> </div> + <div class="footer-section links"> + <h3 class="footerTitle" data-translate-key="footer.quick_links">Quick Links</h3> + <ul class="footerLinks"> + <li><a th:href="@{/aboutus}" class="footerLink" data-translate-key="navbar.about_us">About Us</a></li> + <li><a th:href="@{/contact}" class="footerLink" data-translate-key="navbar.contact">Contact</a></li> + <li><a th:href="@{/}" class="footerLink" data-translate-key="footer.privacy_policy">Privacy Policy</a></li> + <li><a th:href="@{/}" class="footerLink" data-translate-key="footer.terms_of_service">Terms of Service</a> + </li> + </ul> + </div> - - <footer class="footer"> - <div class="footer-section about"> - <h3 class="footerTitle">Polish Community Website</h3> - <p class="footerText">Connecting people, sharing ideas, and building a better future together.</p> - </div> - - <div class="footer-section links"> - <h3 class="footerTitle">Quick Links</h3> - <ul class="footerLinks"> - <li><a th:href="@{/aboutus}" class="footerLink">About Us</a></li> - <li><a th:href="@{/contact}" class="footerLink">Contact</a></li> - <li><a th:href="@{/}" class="footerLink">Privacy Policy</a></li> - <li><a th:href="@{/}" class="footerLink">Terms of Service</a></li> - </ul> - </div> - - <div class="footer-section connect"> - <h3 class="footerTitle">Connect with us</h3> - <div class="social-icons"> - <a href="https://www.facebook.com/LudekPCG" class="social-icon"><img src="/assets/navbarImages/facebook.png" alt="Facebook"></a> - <a href="#" class="social-icon"><img src="/assets/navbarImages/twitter.png" alt="Twitter"></a> - <a href="#" class="social-icon"><img src="/assets/navbarImages/instagram.png" alt="Instagram"></a> - </div> - </div> - - <div class="footer-section copyright"> - <p class="footerCompanyName">© LUDEK PCG ltd. All rights reserved.</p> + <div class="footer-section connect"> + <h3 class="footerTitle" data-translate-key="footer.connect_with_us">Connect with us</h3> + <div class="social-icons"> + <a href="https://www.facebook.com/LudekPCG" class="social-icon"><img src="/assets/navbarImages/facebook.png" + alt="Facebook"></a> + <a href="#" class="social-icon"><img src="/assets/navbarImages/twitter.png" alt="Twitter"></a> + <a href="#" class="social-icon"><img src="/assets/navbarImages/instagram.png" alt="Instagram"></a> </div> - </footer> - <script th:replace="~{comments/commentFragment::commentScript}"></script> + </div> + <div class="footer-section copyright"> + <p class="footerCompanyName">© LUDEK PCG ltd. <span data-translate-key="footer.all_rights_reserved" >All rights reserved.</span></p> + </div> +</footer> +<script src="/js/layout/layout.js" defer></script> +<script th:replace="~{comments/commentFragment::commentScript}"></script> </body> </html> \ No newline at end of file diff --git a/src/main/resources/translations.sql b/src/main/resources/translations.sql new file mode 100644 index 0000000000000000000000000000000000000000..1ef42bbaf33963cb21d6fb99877771d42a6a5346 --- /dev/null +++ b/src/main/resources/translations.sql @@ -0,0 +1,54 @@ +INSERT INTO translations (translation_key, language, value) VALUES + -- English translations + ('navbar.home', 'en', 'Home'), + ('navbar.news', 'en', 'News'), + ('navbar.events', 'en', 'Events'), + ('navbar.db_info', 'en', 'Info Database'), + ('navbar.contact_us', 'en', 'Contact Us'), + ('navbar.feed', 'en', 'Feed'), + ('navbar.about_us', 'en', 'About Us'), + ('navbar.profile', 'en', 'Profile'), + ('home.header', 'en', 'Welcome to our Community'), + ('home.smallheader', 'en', 'Connect, Share, and Grow Together'), + ('home.latest_posts', 'en', 'Stay updated with the latest posts'), + ('home.news', 'en', 'Discover the latest community news'), + ('home.events', 'en', 'Join our upcoming community events'), + ('home.db_info', 'en', 'Access our community information database'), + ('home.about_us', 'en', 'Learn about our mission and values'), + ('home.join_community', 'en', 'Join Our Thriving Community Today!'), + ('home.tagline', 'en', 'Connect with like-minded individuals, share your ideas, and be part of something amazing'), + ('home.get_started', 'en', 'Get Started'), + ('footer.tagline', 'en', 'Connecting people, sharing ideas, and building a better future together.'), + ('footer.quick_links', 'en', 'Quick Links'), + ('footer.connect_with_us', 'en', 'Connect with us'), + ('footer.privacy_policy', 'en', 'Privacy Policy'), + ('footer.terms_of_service', 'en', 'Terms of Service'), + ('footer.all_rights_reserved', 'en', 'All rights reserved.'), + + -- Polish translations + ('navbar.home', 'pl', 'Strona główna'), + ('navbar.news', 'pl', 'AktualnoÅ›ci'), + ('navbar.events', 'pl', 'Wydarzenia'), + ('navbar.db_info', 'pl', 'Informacje o bazie danych'), + ('navbar.contact_us', 'pl', 'Skontaktuj siÄ™ z nami'), + ('navbar.feed', 'pl', 'KanaÅ‚'), + ('navbar.about_us', 'pl', 'O nas'), + ('navbar.profile', 'pl', 'Profil'), + ('home.header', 'pl', 'Witamy w naszej spoÅ‚ecznoÅ›ci'), + ('home.smallheader', 'pl', 'ÅÄ…cz siÄ™, dziel i rozwijaj razem'), + ('home.latest_posts', 'pl', 'BÄ…dź na bieżąco z najnowszymi postami'), + ('home.news', 'pl', 'Odkryj najnowsze wiadomoÅ›ci spoÅ‚ecznoÅ›ciowe'), + ('home.events', 'pl', 'Dołącz do nadchodzÄ…cych wydarzeÅ„ spoÅ‚ecznoÅ›ciowych'), + ('home.db_info', 'pl', 'Uzyskaj dostÄ™p do naszej bazy informacji spoÅ‚ecznoÅ›ciowych'), + ('home.about_us', 'pl', 'Dowiedz siÄ™ o naszej misji i wartoÅ›ciach'), + ('home.join_community', 'pl', 'Dołącz do naszej rozwijajÄ…cej siÄ™ spoÅ‚ecznoÅ›ci już dziÅ›!'), + ('home.tagline', 'pl', 'ÅÄ…cz siÄ™ z podobnie myÅ›lÄ…cymi ludźmi, dziel siÄ™ pomysÅ‚ami i bÄ…dź częściÄ… czegoÅ› niesamowitego'), + ('home.get_started', 'pl', 'Zacznij teraz'), + ('footer.tagline', 'pl', 'ÅÄ…czÄ…c ludzi, dzielÄ…c siÄ™ pomysÅ‚ami i budujÄ…c lepszÄ… przyszÅ‚ość razem.'), + ('footer.quick_links', 'pl', 'Szybkie linki'), + ('footer.connect_with_us', 'pl', 'Połącz siÄ™ z nami'), + ('footer.privacy_policy', 'pl', 'Polityka prywatnoÅ›ci'), + ('footer.terms_of_service', 'pl', 'Warunki korzystania z usÅ‚ugi'), + ('footer.all_rights_reserved', 'pl', 'Wszelkie prawa zastrzeżone.'); + + diff --git a/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java b/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..187f7c4925e51412887b4649ca95b2ca67b2e911 --- /dev/null +++ b/src/test/java/polish_community_group_11/polish_community/translation/controller/TranslationControllerTest.java @@ -0,0 +1,85 @@ +package polish_community_group_11.polish_community.translation.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.service.TranslationService; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@WebMvcTest(TranslationApisController.class) +class TranslationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private TranslationService translationService; + + private List<Translation> mockTranslations; + + @BeforeEach + void setUp() { + mockTranslations = Arrays.asList( + new Translation(1, "greeting", "en", "Hello"), + new Translation(2, "greeting", "pl", "Cześć") + ); + } + + @Test + void getAllTranslations_shouldReturnAllTranslations() throws Exception { + + when(translationService.getAllTranslations()).thenReturn(mockTranslations); + mockMvc.perform(get("/api/translations") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[0].key", is("greeting"))) + .andExpect(jsonPath("$[0].value", is("Hello"))); + } + + @Test + void getTranslationsByLanguage_shouldReturnTranslationsForSpecificLanguage() throws Exception { + List<Translation> englishTranslations = List.of( + new Translation(1, "greeting", "en", "Hello") + ); + + when(translationService.getTranslationsByLanguage("en")).thenReturn(englishTranslations); + + mockMvc.perform(get("/api/translations/language/en") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].value", is("Hello"))); + } + + @Test + void addTranslation_shouldAddNewTranslation() throws Exception { + Translation newTranslation = new Translation(0, "farewell", "en", "Goodbye"); + + + Mockito.doNothing().when(translationService).addTranslation(Mockito.any(Translation.class)); + + mockMvc.perform(post("/api/translations") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"key\":\"farewell\",\"language\":\"en\",\"value\":\"Goodbye\"}")) + .andExpect(status().isOk()) + .andExpect(content().string("Translation added successfully.")); + } + + +} diff --git a/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java b/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..18dad83d2eba0a5f25f7398e565f48fbcd1a23c1 --- /dev/null +++ b/src/test/java/polish_community_group_11/polish_community/translation/service/TranslationServiceImplTest.java @@ -0,0 +1,42 @@ +package polish_community_group_11.polish_community.translation.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import polish_community_group_11.polish_community.translation.model.Translation; +import polish_community_group_11.polish_community.translation.repository.TranslationRepository; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class TranslationServiceImplTest { + + @Mock + private TranslationRepository translationRepository; + + @InjectMocks + private TranslationServiceImpl translationService; + + private Translation mockTranslation; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + mockTranslation = new Translation(1, "greeting", "en", "Hello"); + } + + @Test + void getAllTranslations_shouldReturnAllTranslations() { + when(translationRepository.getAllTranslations()).thenReturn(List.of(mockTranslation)); + + List<Translation> result = translationService.getAllTranslations(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Hello", result.get(0).getValue()); + } +}