diff --git a/domain/src/main/java/org/fao/geonet/domain/Language.java b/domain/src/main/java/org/fao/geonet/domain/Language.java index 466c153c044..4bf91051beb 100644 --- a/domain/src/main/java/org/fao/geonet/domain/Language.java +++ b/domain/src/main/java/org/fao/geonet/domain/Language.java @@ -114,7 +114,7 @@ protected void setInspire_JPAWorkaround(char isinspire) { } /** - * Return true if this is a language required byt the inspire standards. + * Return true if this is a language required by the inspire standards. * * @return return true if required by inspire. */ diff --git a/services/src/main/java/org/fao/geonet/api/languages/LanguagesApi.java b/services/src/main/java/org/fao/geonet/api/languages/LanguagesApi.java index ad6b53f85e2..84db9b94e91 100644 --- a/services/src/main/java/org/fao/geonet/api/languages/LanguagesApi.java +++ b/services/src/main/java/org/fao/geonet/api/languages/LanguagesApi.java @@ -28,8 +28,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jeeves.server.context.ServiceContext; +import org.fao.geonet.ApplicationContextHolder; import org.fao.geonet.api.ApiParams; import org.fao.geonet.api.ApiUtils; +import org.fao.geonet.api.exception.NotAllowedException; import org.fao.geonet.api.exception.ResourceNotFoundException; import org.fao.geonet.domain.Language; import org.fao.geonet.kernel.GeonetworkDataDirectory; @@ -41,14 +43,15 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @RequestMapping(value = { "/{portal}/api/languages" @@ -64,6 +67,37 @@ public class LanguagesApi { @Autowired private GeonetworkDataDirectory dataDirectory; + private String defaultLanguage; + + @Resource(name="defaultLanguage") + public void setDefaultLanguage(String defaultLanguage) { + this.defaultLanguage = defaultLanguage; + } + + @io.swagger.v3.oas.annotations.Operation( + summary = "Get languages available in the application", + description = "Languages available in this version of the application. Those that you can add using PUT operation and which have SQL script to initialize the language.") + @RequestMapping( + value = "/application", + produces = MediaType.APPLICATION_JSON_VALUE, + method = RequestMethod.GET) + @ResponseStatus(value = HttpStatus.OK) + @PreAuthorize("hasAuthority('Administrator')") + @ResponseBody + public List getApplicationLanguages() throws Exception { + Set applicationLanguages = + (Set) ApplicationContextHolder.get().getBean("languages"); + List list = applicationLanguages.stream().map(l -> { + Language language = new Language(); + language.setId(l); + if (l.equals(defaultLanguage)) { + language.setDefaultLanguage(true); + } + return language; + }).collect(Collectors.toList()); + return list; + } + @io.swagger.v3.oas.annotations.Operation( summary = "Get languages", @@ -109,8 +143,8 @@ public void addLanguages( HttpServletRequest request ) throws IOException, ResourceNotFoundException { - Language lang = languageRepository.findById(langCode).get(); - if (lang == null) { + Optional lang = languageRepository.findById(langCode); + if (!lang.isPresent()) { String languageDataFile = "loc-" + langCode + "-default.sql"; Path templateFile = dataDirectory.getWebappDir().resolve("WEB-INF") .resolve("classes").resolve("setup").resolve("sql").resolve("data") @@ -134,7 +168,7 @@ public void addLanguages( )); } else { throw new RuntimeException(String.format( - "Language '%s' already available.", lang.getId() + "Language '%s' already available.", lang.get().getId() )); } } @@ -166,12 +200,19 @@ public void deleteLanguage( String langCode, HttpServletRequest request ) throws IOException, ResourceNotFoundException { - Language lang = languageRepository.findById(langCode).get(); - if (lang == null) { + Optional lang = languageRepository.findById(langCode); + if (!lang.isPresent()) { throw new ResourceNotFoundException(String.format( "Language '%s' not found.", langCode )); } else { + long count = languageRepository.count(); + if (count == 1) { + throw new NotAllowedException(String.format( + "You can't delete the last language. Add another one before removing %s.", + langCode + )); + } final String LANGUAGE_DELETE_SQL = "language-delete.sql"; Path templateFile = dataDirectory.getWebappDir().resolve("WEB-INF") @@ -182,7 +223,7 @@ public void deleteLanguage( try (BufferedReader br = new BufferedReader(new FileReader(templateFile.toFile()))) { String line; while ((line = br.readLine()) != null) { - data.add(String.format(line, lang.getId())); + data.add(String.format(line, lang.get().getId())); } } if (data.size() > 0) { diff --git a/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java b/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java index ff4fc1bea4e..5d7d046399f 100644 --- a/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java +++ b/services/src/main/java/org/fao/geonet/api/tools/i18n/TranslationApi.java @@ -26,8 +26,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jeeves.server.context.ServiceContext; -import org.apache.commons.lang.StringUtils; -import org.fao.geonet.ApplicationContextHolder; import org.fao.geonet.api.ApiUtils; import org.fao.geonet.api.exception.ResourceNotFoundException; import org.fao.geonet.domain.*; @@ -36,7 +34,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -46,7 +43,9 @@ import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.util.*; +import java.util.stream.Collectors; +import static java.util.stream.Collectors.groupingBy; import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; @@ -75,15 +74,96 @@ public synchronized void setApplicationContext(ApplicationContext context) { this.context = context; } + /** + * @param type The type of object to return. + * @return A map of translations in JSON format. + */ + @io.swagger.v3.oas.annotations.Operation( + summary = "List translations for database description table") + @RequestMapping(value = "/db", + method = RequestMethod.GET, + produces = { + MediaType.APPLICATION_JSON_VALUE + }) + @ResponseBody + public Map getTranslations( + @RequestParam(required = false) final List type, + ServletRequest request + ) throws Exception { + Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); + String language = languageUtils.locale2gnCode(locale.getISO3Language()); + return translationPackBuilder.getDbTranslation(language, type); + } + + @io.swagger.v3.oas.annotations.Operation(summary = "List custom user translations") + @RequestMapping(value = "/db/custom", + method = RequestMethod.GET, + produces = { + MediaType.APPLICATION_JSON_VALUE + }) + @ResponseBody + public List getCustomTranslations( + @RequestParam(required = false) final List type, + ServletRequest request + ) throws Exception { + return translationsRepository.findAll(); + } + @io.swagger.v3.oas.annotations.Operation( - summary = "Add or update database translations.") - @PutMapping(value = "/db/translations/{key}", + summary = "Add or update all database translations.") + @PutMapping(value = "/db/translations", produces = { MediaType.APPLICATION_JSON_VALUE }) @PreAuthorize("hasAuthority('Administrator')") @ResponseStatus(CREATED) public ResponseEntity addTranslations( + @Parameter( + name = "values" + ) + @RequestBody(required = true) + final List values, + @RequestParam(required = false) + final boolean replace + ) throws Exception { + if (replace) { + translationsRepository.deleteAll(); + } + + Map> translations = values.stream() + .filter(Objects::nonNull) + .collect(groupingBy(Translations::getFieldName, + Collectors.toMap(Translations::getLangId, Translations::getValue))); + + translations.forEach((key, t) -> { + updateTranslation(key, t); + }); + translationPackBuilder.clearCache(); + return new ResponseEntity(HttpStatus.CREATED); + } + + @io.swagger.v3.oas.annotations.Operation( + summary = "Remove all database translations.") + @DeleteMapping(value = "/db/translations", + produces = { + MediaType.APPLICATION_JSON_VALUE + }) + @PreAuthorize("hasAuthority('Administrator')") + @ResponseStatus(CREATED) + public void removeAllTranslations() throws Exception { + translationsRepository.deleteAll(); + translationPackBuilder.clearCache(); + } + + @io.swagger.v3.oas.annotations.Operation( + summary = "Add or update database translations for a key.") + @PutMapping(value = "/db/translations/{key:.+}", + produces = { + MediaType.APPLICATION_JSON_VALUE + }) + @PreAuthorize("hasAuthority('Administrator')") + @ResponseStatus(CREATED) + public ResponseEntity addTranslationsFor( @PathVariable final String key, @Parameter( @@ -100,6 +180,13 @@ public ResponseEntity addTranslations( translationsRepository.findAllByFieldName(key) ); } + updateTranslation(key, values); + translationPackBuilder.clearCache(); + return new ResponseEntity(HttpStatus.CREATED); + } + + + private void updateTranslation(String key, Map values) { List translations = translationsRepository.findAllByFieldName(key); if(translations.size() == 0) { values.forEach((l, v) -> { @@ -117,12 +204,13 @@ public ResponseEntity addTranslations( }); translationsRepository.saveAll(translations); } - return new ResponseEntity(HttpStatus.CREATED); + translationPackBuilder.clearCache(); } + @io.swagger.v3.oas.annotations.Operation( summary = "Delete database translations.") - @DeleteMapping(value = "/db/translations/{key}", + @DeleteMapping(value = "/db/translations/{key:.+}", produces = { MediaType.APPLICATION_JSON_VALUE }) @@ -142,6 +230,7 @@ public void deleteTranslations( key, language)); } else { translationsRepository.deleteInBatch(translations); + translationPackBuilder.clearCache(); } } @@ -163,27 +252,6 @@ public Map getDbTranslations( } - /** - * @param type The type of object to return. - * @return A map of translations in JSON format. - */ - @io.swagger.v3.oas.annotations.Operation(summary = "List translations for database description table") - @RequestMapping(value = "/db", - method = RequestMethod.GET, - produces = { - MediaType.APPLICATION_JSON_VALUE - }) - @ResponseBody - public Map getTranslations( - @RequestParam(required = false) final List type, - ServletRequest request - ) throws Exception { - Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); - String language = languageUtils.locale2gnCode(locale.getISO3Language()); - return translationPackBuilder.getDbTranslation(language, type); - } - - /** * Get list of packages. */ diff --git a/services/src/main/resources/config-spring-geonetwork.xml b/services/src/main/resources/config-spring-geonetwork.xml index da43e355648..eb68f7bcf01 100644 --- a/services/src/main/resources/config-spring-geonetwork.xml +++ b/services/src/main/resources/config-spring-geonetwork.xml @@ -170,7 +170,7 @@ json/gnui standards/iso19139/codelists/gmd:MD_TopicCategoryCode+gmd:MD_ScopeCode+gmd:MD_MaintenanceFrequencyCode+gmd:MD_ProgressCode+gmd:DS_InitiativeTypeCode+gmd:MD_SpatialRepresentationTypeCode standards/iso19115-3.2018/codelists/cit:CI_DateTypeCode - db/MetadataCategory+Operation+Group+StatusValue+Source + db/MetadataCategory+Operation+Group+StatusValue+Source+Translations @@ -184,14 +184,14 @@ json/schemas standards/iso19139/codelists/gmd:MD_TopicCategoryCode+gmd:MD_ScopeCode+gmd:MD_MaintenanceFrequencyCode+gmd:MD_ProgressCode+gmd:DS_InitiativeTypeCode+gmd:MD_SpatialRepresentationTypeCode standards/iso19115-3.2018/codelists/cit:CI_DateTypeCode+indeterminatePosition - db/MetadataCategory+Operation+Group+StatusValue+Source + db/MetadataCategory+Operation+Group+StatusValue+Source+Translations json/core+search+admin+v4 json/schemas - db/MetadataCategory+Operation+Group+StatusValue+Source + db/MetadataCategory+Operation+Group+StatusValue+Source+Translations @@ -200,7 +200,7 @@ json/schemas standards/iso19139/codelists/gmd:MD_TopicCategoryCode+gmd:MD_ScopeCode+gmd:MD_MaintenanceFrequencyCode+gmd:MD_ProgressCode+gmd:DS_InitiativeTypeCode+gmd:MD_SpatialRepresentationTypeCode standards/iso19115-3.2018/codelists/cit:CI_DateTypeCode - db/MetadataCategory+Operation+Group+StatusValue+Source + db/MetadataCategory+Operation+Group+StatusValue+Source+Translations diff --git a/web-ui/src/main/resources/catalog/js/admin/LanguagesController.js b/web-ui/src/main/resources/catalog/js/admin/LanguagesController.js new file mode 100644 index 00000000000..512aef2f449 --- /dev/null +++ b/web-ui/src/main/resources/catalog/js/admin/LanguagesController.js @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * United Nations (FAO-UN), United Nations World Food Programme (WFP) + * and United Nations Environment Programme (UNEP) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2, + * Rome - Italy. email: geonetwork@osgeo.org + */ + +(function() { + goog.provide('gn_languages_controller'); + + + var module = angular.module('gn_languages_controller', + []); + + + module.controller('GnLanguagesController', [ + '$scope', '$http', '$rootScope', '$translate', + function($scope, $http, $rootScope, $translate) { + $scope.dbLanguages = []; + $scope.applicationLanguagesNotAlreadyAvailable = []; + + function loadDbLanguages() { + $http.get('../api/languages').then(function(r) { + $scope.dbLanguages = r.data; + $http.get('../api/languages/application').then(function(r) { + $scope.applicationLanguagesNotAlreadyAvailable = r.data.filter(function(l) { + return $scope.dbLanguages.find(function(dbL) { + return dbL.id === l.id}) === undefined; + }); + }); + }); + } + + $scope.removeLanguage = function(l) { + $http.delete('../api/languages/' + l.id).then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('language.removed'), + timeout: 2, + type: 'success'}); + loadDbLanguages(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('language.removal.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + loadDbLanguages(); + }); + }; + $scope.addLanguage = function(l) { + $http.put('../api/languages/' + l.id).then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('language.added'), + timeout: 2, + type: 'success'}); + loadDbLanguages(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('language.added.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + loadDbLanguages(); + }); + }; + loadDbLanguages(); + + + + $scope.dbTranslations = []; + $scope.newKey = ''; + + function loadDbTranslations() { + $http.get('../api/i18n/db/custom').then(function(r) { + $scope.dbTranslations = r.data; + }); + }; + + $scope.saveTranslations = function(l) { + $http.put('../api/i18n/db/translations', + $scope.dbTranslations).then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('translations.saved'), + timeout: 2, + type: 'success'}); + loadDbTranslations(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('translations.save.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + }); + }; + + $scope.removeAllTranslations = function(l) { + $http.delete('../api/i18n/db/translations').then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('translations.allremoved'), + timeout: 2, + type: 'success'}); + loadDbTranslations(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('translations.allremoval.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + }); + }; + + $scope.synchAllTranslations = function() { + var translationByKey = _.groupBy($scope.dbTranslations, 'fieldName'); + angular.forEach(translationByKey, function (values, key) { + var translationByLang = _.groupBy(values, 'langId'); + for (var j = 0; j < $scope.dbLanguages.length; j++) { + // Add missing + var lang = $scope.dbLanguages[j].id; + if (!translationByLang[lang]) { + $scope.dbTranslations.push({ + fieldName: key, + langId: lang, + value: '' + }); + } + } + }); + } + + function buildTranslationObject() { + var translationObject = {}; + for (var i = 0; i < $scope.dbLanguages.length; i ++) { + translationObject[$scope.dbLanguages[i].id] = ''; + } + return translationObject; + } + + $scope.addTranslation = function(newKey) { + if(newKey != '') { + $http.put('../api/i18n/db/translations/' + newKey, + buildTranslationObject()).then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('translations.saved'), + timeout: 2, + type: 'success'}); + $scope.newKey = ''; + loadDbTranslations(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('translations.save.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + }); + } + }; + + $scope.removeTranslation = function(key) { + if(key != '') { + $http.delete('../api/i18n/db/translations/' + key).then(function(r) { + $rootScope.$broadcast('StatusUpdated', { + msg: $translate.instant('translations.removed'), + timeout: 2, + type: 'success'}); + $scope.newKey = ''; + loadDbTranslations(); + }, function(r) { + $rootScope.$broadcast('StatusUpdated', { + title: $translate.instant('translations.removal.error'), + error: r.data, + timeout: 0, + type: 'danger'}); + }); + } + }; + loadDbTranslations(); + }]); +})(); diff --git a/web-ui/src/main/resources/catalog/js/admin/SettingsController.js b/web-ui/src/main/resources/catalog/js/admin/SettingsController.js index 5df1e53108e..d57963b4769 100644 --- a/web-ui/src/main/resources/catalog/js/admin/SettingsController.js +++ b/web-ui/src/main/resources/catalog/js/admin/SettingsController.js @@ -43,10 +43,12 @@ goog.require('gn_scroll_spy'); goog.require('gn_sources_controller'); goog.require('gn_system_settings_controller'); + goog.require('gn_languages_controller'); var module = angular.module('gn_settings_controller', ['gn_system_settings_controller', 'gn_csw_settings_controller', + 'gn_languages_controller', 'gn_mapserver_controller', 'gn_csw_test_controller', 'gn_logo_settings_controller', @@ -100,6 +102,11 @@ icon: 'fa-database', label: 'manageSources', href: '#/settings/sources' + },{ + type: 'languages', + icon: 'fa-comments', + label: 'languagesAndTranslations.manage', + href: '#/settings/languages' },{ type: 'csw', label: 'manageCSW', diff --git a/web-ui/src/main/resources/catalog/locales/en-v4.json b/web-ui/src/main/resources/catalog/locales/en-v4.json index 2d7b0f0b7ee..0d4155ccadb 100644 --- a/web-ui/src/main/resources/catalog/locales/en-v4.json +++ b/web-ui/src/main/resources/catalog/locales/en-v4.json @@ -7,6 +7,33 @@ "otherKeywords-": "Keywords", "link-datasets": "Service", "link-services": "Service", + "languagesAndTranslations.manage": "Languages & translations", + "languages.manage": "Registered languages in database", + "languages.manage.help": "Language stored in database are used to store translations for database entity like group names, portal titles, ... Limiting the list of languages to the minimum simplify admin console entry forms.", + "translations.manage": "Manage translations", + "language.add": "Add other languages", + "language.added": "Language added.", + "language.added.error": "Error while adding language.", + "language.removed": "Language removed.", + "language.removal.error": "Error while removing language.", + "language.name": "Language", + "language.id": "Identifier", + "language.remove": "Remove language", + "translation.addFor": "Add a new translation for", + "translation.add": "Add", + "translation.added": "Translation added", + "translation.added.error": "Error while adding translation", + "translations.remove.for": "Remove translation for ", + "translations.remove.all": "Remove all", + "translations.synch.all": "Synchronize with db languages", + "translations.synch.all.help": "For each db languages, check a key exist and add it if necessary.", + "translations.removed": "Translation removed", + "translations.removal.error": "Error while removing translation", + "translation.key": "Identifier", + "translation.value": "Translation", + "translations.save": "Save all translations", + "translation.table.view": "Table mode", + "translation.json.view": "JSON mode", "remoteRecord": "Record from external catalog", "chooseACatalogRecord": "Choose a catalog record", "siblingListToAdd": "List of record links to add", diff --git a/web-ui/src/main/resources/catalog/templates/admin/settings/languages.html b/web-ui/src/main/resources/catalog/templates/admin/settings/languages.html new file mode 100644 index 00000000000..c93ea4720b4 --- /dev/null +++ b/web-ui/src/main/resources/catalog/templates/admin/settings/languages.html @@ -0,0 +1,139 @@ +
+
+
+
languages.manage
+
+
languages.manage.help
+ + + + + + + + + + + + + + +
language.namelanguage.id
{{l.name}}{{l.id}} + +
+ +
+ + +
+
+
+
+
+
+
translations.manage
+
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + +
translation.keylanguage.nametranslation.value
+ + {{l.fieldName}} + + + {{l.langId | translate}} + +
+
+ translation.addFor + + + + +
+
+
+ + + +
+
+
+
+
diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/loc-fre-default.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/loc-fre-default.sql index 5fdbc75df38..8ecd0b591f0 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/data/loc-fre-default.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/data/loc-fre-default.sql @@ -1,4 +1,4 @@ -INSERT INTO Languages (id, name, isinspire, isdefault) VALUES ('fre','français', 'y', 'n'); +INSERT INTO Languages (id, name, isinspire, isdefault) VALUES ('fre','Français', 'y', 'n'); -- Take care to table ID (related to other loc files) INSERT INTO CategoriesDes (iddes, langid, label) VALUES (2,'fre','Jeux de données'); diff --git a/web/src/main/webapp/WEB-INF/classes/setup/sql/template/language-delete.sql b/web/src/main/webapp/WEB-INF/classes/setup/sql/template/language-delete.sql index 29e6114d3ec..5463ddafbae 100644 --- a/web/src/main/webapp/WEB-INF/classes/setup/sql/template/language-delete.sql +++ b/web/src/main/webapp/WEB-INF/classes/setup/sql/template/language-delete.sql @@ -1,8 +1,9 @@ -DELETE FROM CswServerCapabilitiesInfo WHERE langid = '%s'; DELETE FROM CategoriesDes WHERE langid = '%s'; DELETE FROM GroupsDes WHERE langid = '%s'; DELETE FROM IsoLanguagesDes WHERE langid = '%s'; DELETE FROM OperationsDes WHERE langid = '%s'; DELETE FROM StatusValuesDes WHERE langid = '%s'; DELETE FROM SourcesDes WHERE langid = '%s'; +DELETE FROM SelectionsDes WHERE langid = '%s'; +DELETE FROM GUF_RatingCriteriaDes WHERE langid = '%s'; DELETE FROM Languages WHERE id = '%s';