Merge remote-tracking branch 'origin/releases/7.0.x' into releases/7.1.x
diff --git a/org.eclipse.scout.sdk.core/src/main/java/org/eclipse/scout/sdk/core/model/spi/internal/WorkspaceFileSystem.java b/org.eclipse.scout.sdk.core/src/main/java/org/eclipse/scout/sdk/core/model/spi/internal/WorkspaceFileSystem.java
index 914dd5a..baf8355 100644
--- a/org.eclipse.scout.sdk.core/src/main/java/org/eclipse/scout/sdk/core/model/spi/internal/WorkspaceFileSystem.java
+++ b/org.eclipse.scout.sdk.core/src/main/java/org/eclipse/scout/sdk/core/model/spi/internal/WorkspaceFileSystem.java
@@ -20,6 +20,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.zip.ZipError;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.batch.ClasspathJar;
@@ -47,13 +48,13 @@
     m_overrideCompilationUnits = new HashMap<>();
     m_additionalPackages = new HashSet<>();
     m_classpaths = paths;
-    try {
-      for (Classpath cp : m_classpaths) {
+    for (Classpath cp : m_classpaths) {
+      try {
         cp.initialize();
       }
-    }
-    catch (IOException e) {
-      throw new SdkException(e);
+      catch (IOException | ZipError e) {
+        throw new SdkException("Unable to initialize classpath " + cp.getPath(), e);
+      }
     }
   }
 
diff --git a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/search/NlsFindMissingKeysJob.java b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/search/NlsFindMissingKeysJob.java
index c97345a..31f5ac5 100644
--- a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/search/NlsFindMissingKeysJob.java
+++ b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/search/NlsFindMissingKeysJob.java
@@ -35,6 +35,7 @@
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
 import org.eclipse.scout.sdk.core.s.IScoutRuntimeTypes;
 import org.eclipse.scout.sdk.core.util.SdkException;
@@ -61,6 +62,7 @@
   private final List<Match> m_matches;
   private final List<Match> m_errorMatches;
   private final String m_textClassName;
+  private final char[] m_ignorePattern;
 
   public NlsFindMissingKeysJob() {
     super("Search for text keys that are used but do not exist.");
@@ -77,6 +79,7 @@
     m_patternsByFileType.put("js", Arrays.asList(jsTextKeyPat, jsonTextKeyPat));
     m_patternsByFileType.put("html", Arrays.asList(Pattern.compile("\\<scout:message key=\"(" + nlsKeyPattern + ")\"\\s*/?\\>"), jsTextKeyPat, jsonTextKeyPat));
 
+    m_ignorePattern = "NO-NLS-CHECK".toCharArray();
     m_textClassName = Signature.getSimpleName(IScoutRuntimeTypes.TEXTS) + SuffixConstants.SUFFIX_STRING_java;
   }
 
@@ -116,7 +119,12 @@
       return;
     }
 
-    final String fileName = file.path().getFileName().toString().toLowerCase();
+    final Path lastSegment = file.path().getFileName();
+    if (lastSegment == null) {
+      return;
+    }
+
+    final String fileName = lastSegment.toString().toLowerCase();
     final int lastDotPos = fileName.lastIndexOf('.');
     final String extension = fileName.substring(lastDotPos + 1);
     final Collection<Pattern> patterns = m_patternsByFileType.get(extension);
@@ -124,8 +132,9 @@
       throw new SdkException("Unexpected: no pattern for file: " + file.path());
     }
 
+    final CharBuffer fileContent = CharBuffer.wrap(file.content());
     for (final Pattern p : patterns) {
-      final Matcher matcher = p.matcher(CharBuffer.wrap(file.content()));
+      final Matcher matcher = p.matcher(fileContent);
       while (matcher.find()) {
         final int keyGroup;
         if (matcher.groupCount() > 1) {
@@ -157,11 +166,24 @@
         .contains(key);
   }
 
-  protected static Match registerMatch(final WorkspaceFile file, final MatchResult matcher, final Collection<Match> targetList, final int keyGroup) {
+  protected boolean isIgnored(final char[] content, final int offset) {
+    int nlPos = CharOperation.indexOf('\n', content, offset);
+    if (nlPos < m_ignorePattern.length) {
+      return false;
+    }
+    if (content[nlPos - 1] == '\r') {
+      nlPos--;
+    }
+    return CharOperation.fragmentEquals(m_ignorePattern, content, nlPos - m_ignorePattern.length, false);
+  }
+
+  protected void registerMatch(final WorkspaceFile file, final MatchResult matcher, final Collection<Match> targetList, final int keyGroup) {
     final int index = matcher.start(keyGroup);
+    if (isIgnored(file.content(), index)) {
+      return;
+    }
     final Match match = new Match(file.inWorkspace().get(), index, matcher.end(keyGroup) - index);
     targetList.add(match);
-    return match;
   }
 
   public List<Match> matches() {
diff --git a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/simpleproject/WorkspaceTranslationFile.java b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/simpleproject/WorkspaceTranslationFile.java
index f6ed0de..4b9ffde 100644
--- a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/simpleproject/WorkspaceTranslationFile.java
+++ b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/internal/simpleproject/WorkspaceTranslationFile.java
@@ -87,12 +87,17 @@
   }
 
   @Override
-  public IStatus remove(String key, IProgressMonitor monitor) {
-    super.setTranslation(key, null, false, monitor);
+  public IStatus remove(String key, boolean fireEvent, IProgressMonitor monitor) {
+    super.setTranslation(key, null, fireEvent, monitor);
     return Status.OK_STATUS;
   }
 
   @Override
+  public IStatus remove(String key, IProgressMonitor monitor) {
+    return remove(key, false, monitor);
+  }
+
+  @Override
   public IStatus updateKey(String oldKey, String newKey, IProgressMonitor monitor) {
     // remove old
     String translation = getTranslation(oldKey);
diff --git a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/project/AbstractNlsProject.java b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/project/AbstractNlsProject.java
index a238199..9ca552a 100644
--- a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/project/AbstractNlsProject.java
+++ b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/project/AbstractNlsProject.java
@@ -460,14 +460,23 @@
 
   private void updateExistingRowInternal(NlsEntry existingRow, INlsEntry row, boolean flush, IProgressMonitor monitor) {
     boolean updated = false;
-    for (Entry<Language, String> entry : row.getAllTranslations().entrySet()) {
-      String existingTranslation = existingRow.getTranslation(entry.getKey());
-      if (!Objects.equals(existingTranslation, entry.getValue())) {
-        ITranslationResource r = getTranslationResource(entry.getKey());
-        if (r != null) {
+    for (final ITranslationResource res : getAllTranslationResources()) {
+      final Language lang = res.getLanguage();
+      if (row.getAllTranslations().containsKey(lang)) {
+        // update
+        final String newText = row.getTranslation(lang);
+        if (!Objects.equals(existingRow.getTranslation(lang), newText)) {
           updated = true;
-          r.updateText(row.getKey(), entry.getValue(), flush, monitor);
-          existingRow.addTranslation(entry.getKey(), entry.getValue());
+          res.updateText(row.getKey(), newText, flush, monitor);
+          existingRow.addTranslation(lang, newText);
+        }
+      }
+      else {
+        // remove
+        if (res.getAllKeys().contains(row.getKey())) {
+          res.remove(row.getKey(), flush, monitor);
+          existingRow.removeTranslation(lang);
+          updated = true;
         }
       }
     }
diff --git a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/AbstractTranslationResource.java b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/AbstractTranslationResource.java
index 92e6a14..1f8b838 100644
--- a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/AbstractTranslationResource.java
+++ b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/AbstractTranslationResource.java
@@ -12,8 +12,8 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -138,7 +138,7 @@
 
   @Override
   public Set<String> getAllKeys() {
-    return new HashSet<>(m_entries.keySet());
+    return Collections.unmodifiableSet(m_entries.keySet());
   }
 
   protected void setTranslation(String key, String translation, IProgressMonitor monitor) {
@@ -202,4 +202,9 @@
   public IStatus remove(String key, IProgressMonitor monitor) {
     throw new UnsupportedOperationException("this method is not supported on : " + this.getClass().getSimpleName() + " readOnly=" + isReadOnly());
   }
+
+  @Override
+  public IStatus remove(String key, boolean fireEvent, IProgressMonitor monitor) {
+    throw new UnsupportedOperationException("this method is not supported on : " + this.getClass().getSimpleName() + " readOnly=" + isReadOnly());
+  }
 }
diff --git a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/ITranslationResource.java b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/ITranslationResource.java
index b8d2431..41b9a1a 100644
--- a/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/ITranslationResource.java
+++ b/org.eclipse.scout.sdk.s2e.nls/src/main/java/org/eclipse/scout/sdk/s2e/nls/resource/ITranslationResource.java
@@ -62,6 +62,8 @@
    */
   IStatus remove(String key, IProgressMonitor monitor);
 
+  IStatus remove(String key, boolean fireEvent, IProgressMonitor monitor);
+
   /**
    * @param oldKey
    * @param newKey