]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3581 Tool to validate a l10n bundle based on multiple plugins
authorFabrice Bellingard <bellingard@gmail.com>
Fri, 22 Jun 2012 12:04:44 +0000 (14:04 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Fri, 22 Jun 2012 12:04:44 +0000 (14:04 +0200)
sonar-testing-harness/src/main/java/org/sonar/test/i18n/BundleSynchronizedMatcher.java
sonar-testing-harness/src/main/java/org/sonar/test/i18n/I18nMatchers.java
sonar-testing-harness/src/test/java/org/sonar/test/i18n/BundleSynchronizedTest.java
sonar-testing-harness/src/test/java/org/sonar/test/i18n/I18nMatchersTest.java [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/abacus_fr.properties [new file with mode: 0644]

index 7ed8b3384a247bbb7611e6e5c18daf7dfb2606ee..4ae41847827f831bed7d2d1d1b9db656b7d15a3d 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.test.i18n;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.commons.io.IOUtils;
@@ -26,8 +27,16 @@ import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.sonar.test.TestUtils;
 
-import java.io.*;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.util.Collection;
 import java.util.Map;
@@ -42,22 +51,32 @@ import static org.junit.Assert.fail;
 public class BundleSynchronizedMatcher extends BaseMatcher<String> {
 
   public static final String L10N_PATH = "/org/sonar/l10n/";
-  private static final String GITHUB_RAW_FILE_PATH = "https://raw.github.com/SonarSource/sonar/master/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/";
   private static final Collection<String> CORE_BUNDLES = Lists.newArrayList("checkstyle.properties", "core.properties",
-    "findbugs.properties", "gwt.properties", "pmd.properties", "squidjava.properties");
+      "findbugs.properties", "gwt.properties", "pmd.properties", "squidjava.properties");
+  private static final String GITHUB_RAW_FILE_PATH = "https://raw.github.com/SonarSource/sonar/master/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/";
 
   private String sonarVersion;
+  private URI referenceEnglishBundleURI;
   // we use this variable to be able to unit test this class without looking at the real Github core bundles that change all the time
   private String remote_file_path;
   private String bundleName;
   private SortedMap<String, String> missingKeys;
   private SortedMap<String, String> additionalKeys;
 
+  public BundleSynchronizedMatcher() {
+    this(null, GITHUB_RAW_FILE_PATH);
+  }
+
+  public BundleSynchronizedMatcher(URI referenceEnglishBundleURI) {
+    this.referenceEnglishBundleURI = referenceEnglishBundleURI;
+  }
+
   public BundleSynchronizedMatcher(String sonarVersion) {
     this(sonarVersion, GITHUB_RAW_FILE_PATH);
   }
 
-  public BundleSynchronizedMatcher(String sonarVersion, String remote_file_path) {
+  @VisibleForTesting
+  BundleSynchronizedMatcher(String sonarVersion, String remote_file_path) {
     this.sonarVersion = sonarVersion;
     this.remote_file_path = remote_file_path;
   }
@@ -75,6 +94,8 @@ public class BundleSynchronizedMatcher extends BaseMatcher<String> {
     File defaultBundle;
     if (isCoreBundle(defaultBundleName)) {
       defaultBundle = getBundleFileFromGithub(defaultBundleName);
+    } else if (referenceEnglishBundleURI != null) {
+      defaultBundle = getBundleFileFromProvidedURI(defaultBundleName);
     } else {
       defaultBundle = getBundleFileFromClasspath(defaultBundleName);
     }
@@ -167,18 +188,14 @@ public class BundleSynchronizedMatcher extends BaseMatcher<String> {
   }
 
   protected File getBundleFileFromGithub(String defaultBundleName) {
-    File localBundle = new File("target/l10n/download/" + defaultBundleName);
+    String remoteFile = computeGitHubURL(defaultBundleName, sonarVersion);
+    URL remoteFileURL = null;
     try {
-      String remoteFile = computeGitHubURL(defaultBundleName, sonarVersion);
-      saveUrlToLocalFile(remoteFile, localBundle);
+      remoteFileURL = new URL(remoteFile);
     } catch (MalformedURLException e) {
       fail("Could not download the original core bundle at: " + remote_file_path + defaultBundleName);
-    } catch (IOException e) {
-      fail("Could not download the original core bundle at: " + remote_file_path + defaultBundleName);
     }
-    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", localBundle, notNullValue());
-    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", localBundle.exists(), is(true));
-    return localBundle;
+    return downloadRemoteFile(defaultBundleName, remoteFileURL);
   }
 
   protected String computeGitHubURL(String defaultBundleName, String sonarVersion) {
@@ -196,18 +213,29 @@ public class BundleSynchronizedMatcher extends BaseMatcher<String> {
     return bundle;
   }
 
-  protected String extractDefaultBundleName(String bundleName) {
-    int firstUnderScoreIndex = bundleName.indexOf('_');
-    assertThat("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0,
-      is(true));
-    return bundleName.substring(0, firstUnderScoreIndex) + ".properties";
+  private File getBundleFileFromProvidedURI(String defaultBundleName) {
+    URL remoteFileURL = null;
+    try {
+      remoteFileURL = referenceEnglishBundleURI.toURL();
+    } catch (MalformedURLException e) {
+      fail("Could not download the original bundle at: " + remote_file_path + defaultBundleName);
+    }
+    return downloadRemoteFile(defaultBundleName, remoteFileURL);
   }
 
-  protected boolean isCoreBundle(String defaultBundleName) {
-    return CORE_BUNDLES.contains(defaultBundleName);
+  private File downloadRemoteFile(String defaultBundleName, URL remoteFileUrl) {
+    File localBundle = new File("target/l10n/download/" + defaultBundleName);
+    try {
+      saveUrlToLocalFile(remoteFileUrl, localBundle);
+    } catch (IOException e) {
+      fail("Could not download the original core bundle at: " + remoteFileUrl.toString() + defaultBundleName);
+    }
+    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", localBundle, notNullValue());
+    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", localBundle.exists(), is(true));
+    return localBundle;
   }
 
-  private void saveUrlToLocalFile(String url, File localFile) throws IOException {
+  private void saveUrlToLocalFile(URL url, File localFile) throws IOException {
     if (localFile.exists()) {
       localFile.delete();
     }
@@ -216,7 +244,7 @@ public class BundleSynchronizedMatcher extends BaseMatcher<String> {
     InputStream in = null;
     OutputStream fout = null;
     try {
-      in = new BufferedInputStream(new URL(url).openStream());
+      in = new BufferedInputStream(url.openStream());
       fout = new FileOutputStream(localFile);
 
       byte data[] = new byte[1024];
@@ -230,4 +258,15 @@ public class BundleSynchronizedMatcher extends BaseMatcher<String> {
     }
   }
 
+  public static String extractDefaultBundleName(String bundleName) {
+    int firstUnderScoreIndex = bundleName.indexOf('_');
+    assertThat("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0,
+        is(true));
+    return bundleName.substring(0, firstUnderScoreIndex) + ".properties";
+  }
+
+  public static boolean isCoreBundle(String defaultBundleName) {
+    return CORE_BUNDLES.contains(defaultBundleName);
+  }
+
 }
index f053ae11621e7faa20ab11ff295ed6baeb39a52e..ea4ac5f596548c90b07f18f853fa2d4cda99fafa 100644 (file)
  */
 package org.sonar.test.i18n;
 
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
+import com.google.common.collect.Maps;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.test.TestUtils;
 
 import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.test.TestUtils;
-
-import com.google.common.collect.Maps;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 public final class I18nMatchers {
 
@@ -38,6 +39,9 @@ public final class I18nMatchers {
   }
 
   /**
+   * <p>
+   * <b>Used by language packs that translate Core bundles.</b>
+   * </p>
    * Returns a matcher which checks that a translation bundle is up to date with the corresponding English Core bundle.
    * <ul>
    * <li>If a version of Sonar is specified, then the check is done against this version of the bundle found on Sonar Github repository.</li>
@@ -53,46 +57,109 @@ public final class I18nMatchers {
   }
 
   /**
-   * Returns a matcher which checks that a translation bundle is up to date with the corresponding default one found in the same folder. <br>
-   * <br>
-   * This matcher is used for Sonar plugins that embed their own translations.
+   * <p>
+   * <b>Used by language packs that translate third-party bundles.</b>
+   * </p>
+   * Returns a matcher which checks that a translation bundle is up to date with the given reference English bundle from a third-party plugin.
+   * 
+   * @param referenceEnglishBundleURI
+   *          the URI referencing the English bundle to check against
+   * @return the matcher
+   */
+  public static BundleSynchronizedMatcher isBundleUpToDate(URI referenceEnglishBundleURI) {
+    return new BundleSynchronizedMatcher(referenceEnglishBundleURI);
+  }
+
+  /**
+   * <p>
+   * <b>Used only by independent plugins that embeds their own bundles for every language.</b>
+   * </p>
+   * Returns a matcher which checks that a translation bundle is up to date with the corresponding default one found in the same folder.
    * 
    * @return the matcher
    */
   public static BundleSynchronizedMatcher isBundleUpToDate() {
-    return new BundleSynchronizedMatcher(null);
+    return new BundleSynchronizedMatcher();
+  }
+
+  /**
+   * <p>
+   * <b>Must be used only by independent plugins that embeds their own bundles for every language.</b>
+   * </p>
+   * Checks that all the translation bundles found on the classpath are up to date with the corresponding default one found in the same
+   * folder.
+   */
+  public static void assertAllBundlesUpToDate() {
+    try {
+      assertAllBundlesUpToDate(null, null);
+    } catch (URISyntaxException e) {
+      // Ignore, this can't happen here
+    }
   }
 
   /**
-   * Checks that all the Core translation bundles found on the classpath are up to date with the corresponding English ones.
+   * <p>
+   * <b>Must be used only by language packs.</b>
+   * </p>
+   * <p>
+   * Depending on the parameters, this method does the following:
    * <ul>
-   * <li>If a version of Sonar is specified, then the check is done against this version of the bundles found on Sonar Github repository.</li>
-   * <li>If sonarVersion is set to NULL, the check is done against the latest version of this bundles found on Github (master branch).</li>
+   * <li><b>sonarVersion</b>: checks that all the Core translation bundles found on the classpath are up to date with the corresponding English ones found on Sonar 
+   * GitHub repository for the given Sonar version.
+   * <ul><li><i>Note: if sonarVersion is set to NULL, the check is done against the latest version of this bundles found the master branch of the GitHub repository.</i></li></ul>
+   * </li>
+   * <li><b>pluginIdsToBundleUrlMap</b>: checks that other translation bundles found on the classpath are up to date with the reference English bundles of the corresponding
+   * plugins given by the "pluginIdsToBundleUrlMap" parameter.
+   * </li>
    * </ul>
+   * </p>
+   * <p><br>
+   * The following example will check that the translation of the Core bundles are up to date with version 3.2 of Sonar English Language Pack, and it
+   * will also check that the translation of the bundle of the Web plugin is up to date with the reference English bundle of version 1.2 of the Web plugin:
+   * <pre>
+   * Map<String, String> pluginIdsToBundleUrlMap = Maps.newHashMap();
+   * pluginIdsToBundleUrlMap.put("web", "http://svn.codehaus.org/sonar-plugins/tags/sonar-web-plugin-1.2/src/main/resources/org/sonar/l10n/web.properties");
+   * assertAllBundlesUpToDate("3.2", pluginIdsToBundleUrlMap);
+   * </pre>
+   * </p>
    * 
    * @param sonarVersion
    *          the version of the bundles to check against, or NULL to check against the latest source on GitHub
+   * @param pluginIdsToBundleUrlMap
+   *          a map that gives, for a given plugin, the URL of the English bundle that must be used to check the translation.
+   * @throws URISyntaxException if the provided URLs in the "pluginIdsToBundleUrlMap" parameter are not correct
    */
-  public static void assertAllBundlesUpToDate(String sonarVersion) {
+  public static void assertAllBundlesUpToDate(String sonarVersion, Map<String, String> pluginIdsToBundleUrlMap) throws URISyntaxException {
     File bundleFolder = TestUtils.getResource(BundleSynchronizedMatcher.L10N_PATH);
     if (bundleFolder == null || !bundleFolder.isDirectory()) {
       fail("No bundle found in '" + BundleSynchronizedMatcher.L10N_PATH + "'");
     }
 
-    Collection<File> bundles = FileUtils.listFiles(bundleFolder, new String[] { "properties" }, false);
+    Collection<File> bundles = FileUtils.listFiles(bundleFolder, new String[] {"properties"}, false);
     Map<String, String> failedAssertionMessages = Maps.newHashMap();
     for (File bundle : bundles) {
       String bundleName = bundle.getName();
       if (bundleName.indexOf('_') > 0) {
         try {
-          assertThat(bundleName, isBundleUpToDate(sonarVersion));
+          String baseBundleName = BundleSynchronizedMatcher.extractDefaultBundleName(bundleName);
+          String pluginId = StringUtils.substringBefore(baseBundleName, ".");
+          if (BundleSynchronizedMatcher.isCoreBundle(baseBundleName)) {
+            // this is a core bundle => must be checked againt the provided version of Sonar
+            assertThat(bundleName, isBundleUpToDate(sonarVersion));
+          } else if (pluginIdsToBundleUrlMap != null && pluginIdsToBundleUrlMap.get(pluginId) != null) {
+            // this is a third-party plugin translated by a language pack => must be checked against the provided URL
+            assertThat(bundleName, isBundleUpToDate(new URI(pluginIdsToBundleUrlMap.get(pluginId))));
+          } else {
+            // this is the case of a plugin that provides all the bundles for every language => check the bundles inside the plugin
+            assertThat(bundleName, isBundleUpToDate());
+          }
         } catch (AssertionError e) {
           failedAssertionMessages.put(bundleName, e.getMessage());
         }
       }
     }
 
-    if ( !failedAssertionMessages.isEmpty()) {
+    if (!failedAssertionMessages.isEmpty()) {
       StringBuilder message = new StringBuilder();
       message.append(failedAssertionMessages.size());
       message.append(" bundles are not up-to-date: ");
@@ -102,13 +169,4 @@ public final class I18nMatchers {
       fail(message.toString());
     }
   }
-
-  /**
-   * Checks that all the translation bundles found on the classpath are up to date with the corresponding default one found in the same
-   * folder.
-   */
-  public static void assertAllBundlesUpToDate() {
-    assertAllBundlesUpToDate(null);
-  }
-
 }
index 8554d464aae32c5c35b0c76c45e3065af0f41a83..396f196927e2a1a6b1225ff17adcf76b3e26bef5 100644 (file)
@@ -24,50 +24,56 @@ import org.junit.Test;
 import org.sonar.test.TestUtils;
 
 import java.io.File;
+import java.net.URI;
 import java.util.SortedMap;
 
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-import static org.sonar.test.i18n.I18nMatchers.isBundleUpToDate;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 public class BundleSynchronizedTest {
 
-  private static final String GITHUB_RAW_FILE_PATH = "https://raw.github.com/SonarSource/sonar/master/sonar-testing-harness/src/test/resources/org/sonar/l10n/";
+  private static final String GITHUB_RAW_TESTING_FILE_PATH = "https://raw.github.com/SonarSource/sonar/master/sonar-testing-harness/src/test/resources/org/sonar/l10n/";
   private BundleSynchronizedMatcher matcher;
 
   @Before
-  public void test() throws Exception {
-    matcher = new BundleSynchronizedMatcher(null);
+  public void init() {
+    matcher = new BundleSynchronizedMatcher();
   }
 
   @Test
-  // The case of a Sonar plugin that embeds all the bundles for every language
-  public void testBundlesInsideSonarPlugin() {
+  // The case of a Sonar Language Pack that translates the Core bundles
+  public void shouldMatchBundlesOfLanguagePack() {
     // synchronized bundle
-    assertThat("myPlugin_fr_CA.properties", isBundleUpToDate());
-    assertFalse(new File("target/l10n/myPlugin_fr_CA.properties.report.txt").exists());
+    assertThat("core_fr_CA.properties", new BundleSynchronizedMatcher(null, GITHUB_RAW_TESTING_FILE_PATH));
     // missing keys
     try {
-      assertThat("myPlugin_fr.properties", isBundleUpToDate());
-      assertTrue(new File("target/l10n/myPlugin_fr.properties.report.txt").exists());
+      assertThat("core_fr.properties", new BundleSynchronizedMatcher(null, GITHUB_RAW_TESTING_FILE_PATH));
     } catch (AssertionError e) {
       assertThat(e.getMessage(), containsString("Missing translations are:\nsecond.prop"));
     }
   }
 
   @Test
-  public void shouldNotFailIfNoMissingKeysButAdditionalKeys() {
-    assertThat("noMissingKeys_fr.properties", isBundleUpToDate());
+  // The case of a Sonar Language Pack that translates plugin bundles
+  public void shouldMatchWithProvidedURI() throws Exception {
+    matcher = new BundleSynchronizedMatcher(new URI("http://svn.codehaus.org/sonar-plugins/tags/sonar-abacus-plugin-0.1/src/main/resources/org/sonar/l10n/abacus.properties"));
+    assertThat("abacus_fr.properties", matcher);
   }
 
   @Test
-  // The case of a Sonar Language Pack that translates the Core bundles
-  public void testBundlesOfLanguagePack() {
+  // The case of a Sonar plugin that embeds all the bundles for every language
+  public void testBundlesInsideSonarPlugin() {
     // synchronized bundle
-    assertThat("core_fr_CA.properties", new BundleSynchronizedMatcher(null, GITHUB_RAW_FILE_PATH));
+    assertThat("myPlugin_fr_CA.properties", matcher);
+    assertFalse(new File("target/l10n/myPlugin_fr_CA.properties.report.txt").exists());
     // missing keys
     try {
-      assertThat("core_fr.properties", new BundleSynchronizedMatcher(null, GITHUB_RAW_FILE_PATH));
+      assertThat("myPlugin_fr.properties", matcher);
+      assertTrue(new File("target/l10n/myPlugin_fr.properties.report.txt").exists());
     } catch (AssertionError e) {
       assertThat(e.getMessage(), containsString("Missing translations are:\nsecond.prop"));
     }
@@ -85,7 +91,7 @@ public class BundleSynchronizedTest {
 
   @Test
   public void testGetBundleFileFromGithub() throws Exception {
-    matcher = new BundleSynchronizedMatcher(null, GITHUB_RAW_FILE_PATH);
+    matcher = new BundleSynchronizedMatcher(null, GITHUB_RAW_TESTING_FILE_PATH);
     matcher.getBundleFileFromGithub("core.properties");
     assertTrue(new File("target/l10n/download/core.properties").exists());
   }
@@ -133,4 +139,5 @@ public class BundleSynchronizedTest {
     assertThat(matcher.computeGitHubURL("core.properties", "2.10"),
         is("https://raw.github.com/SonarSource/sonar/2.10/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties"));
   }
+
 }
diff --git a/sonar-testing-harness/src/test/java/org/sonar/test/i18n/I18nMatchersTest.java b/sonar-testing-harness/src/test/java/org/sonar/test/i18n/I18nMatchersTest.java
new file mode 100644 (file)
index 0000000..c97cb39
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.test.i18n;
+
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.sonar.test.i18n.I18nMatchers.isBundleUpToDate;
+
+public class I18nMatchersTest {
+
+  @Test
+  public void testBundlesInsideSonarPlugin() {
+    // synchronized bundle
+    assertThat("myPlugin_fr_CA.properties", isBundleUpToDate());
+    assertFalse(new File("target/l10n/myPlugin_fr_CA.properties.report.txt").exists());
+    // missing keys
+    try {
+      assertThat("myPlugin_fr.properties", isBundleUpToDate());
+      assertTrue(new File("target/l10n/myPlugin_fr.properties.report.txt").exists());
+    } catch (AssertionError e) {
+      assertThat(e.getMessage(), containsString("Missing translations are:\nsecond.prop"));
+    }
+  }
+
+  @Test
+  public void shouldNotFailIfNoMissingKeysButAdditionalKeys() {
+    assertThat("noMissingKeys_fr.properties", isBundleUpToDate());
+  }
+}
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/abacus_fr.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/abacus_fr.properties
new file mode 100644 (file)
index 0000000..03ee226
--- /dev/null
@@ -0,0 +1,28 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+widget.abacus.name=Abaques
+widget.abacus.description=Calcule la complexit\u00e9 de chaque composant pour vous aider \u00e0 utiliser vos abaques.
+
+widget.abacus.param.defaultColors=Liste de couleurs (format hexad\u00e9cimal) s\u00e9par\u00e9e par des virgules pour l'affichage du camembert.<br>Bien faire attention \u00e0 ce que le nombre de couleurs soit au minimum le m\u00eame que le nombre de niveaux de complexit\u00e9 des abaques.
+widget.abacus.param.defaultDisplay=Valeurs possibles :<ul class="bullet"><li>files (= nombre de fichiers)</li><li>percentage (= pourcentage)</li></ul>
+
+abacusTab.page=Abaques
+abacusTab.notComputed=Non calcul\u00e9e
+
+property.sonar.abacus.complexityThresholds.name=Seuils de complexit\u00e9 des abaques
+property.sonar.abacus.complexityThresholds.description=Usage : NomSeuil1:Complexit\u00e9Seuil1;NomSeuil2:Complexit\u00e9Seuil2;...;NomSeuilN<br>Valeur par d\u00e9faut : Simple:20;Medium:50;Complex:100;Very Complex
+
+metric.abacus-complexity.name=Complexit\u00e9 (Abaques)
+metric.abacus-complexity.description=Complexit\u00e9 (Abaques)
+metric.abacus-complexity-distribution.name=Distribution de la complexit\u00e9 (Abaques)
+metric.abacus-complexity-distribution.description=Distribution de la complexit\u00e9 (Abaques)
+
+abacus.componentComplexity=Complexit\u00e9 du composant
+abacus.componentComplexityDistribution.numberOfFiles=Distrib. complexit\u00e9 (fichiers)
+abacus.componentComplexityDistribution.percentage=Distrib. complexit\u00e9 (%)
+abacus.error.cannotDisplayWidget=Impossible d'afficher le widget.
+abacus.error.defaultColors.incorrectValue=La valeur du param\u00e8tre de wigdet <i>defaultColors</i> est incorrecte : moins de couleurs que de niveaux de complexit\u00e9 dans les abaques.
+abacus.error.defaultDisplay.incorrectValue=La valeur du param\u00e8tre de widget <i>defaultDisplay</i> est incorrecte : <i>{0}</i><br>Valeurs possibles :<ul class="bullet"><li>files (= nombre de fichiers)</li><li>percentage (= pourcentage)</li></ul>
+abacus.noData=Pas de donn\u00e9es
+abacus.numberOfFilesDistribution=Distribution en nombre de fichiers
+abacus.percentageDistribution=Distribution en %
+abacus.title=Abaques
\ No newline at end of file