This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 5c0f336fec3d24c38d78d4fef8000456b4973d77
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Jul 12 18:04:02 2026 +0200

    Localize the warnings and error messages of the HEIF module.
---
 .../sis/util/resources/IndexedResourceBundle.java  |  16 +-
 .../apache/sis/storage/geoheif/FromImageIO.java    |   5 +-
 .../apache/sis/storage/geoheif/GeoHeifStore.java   |   9 +-
 .../org/apache/sis/storage/geoheif/ImageModel.java |   6 +-
 .../sis/storage/geoheif/ImageResourceBuilder.java  |  31 ++-
 .../sis/storage/geoheif/ResourceBuilder.java       |  38 +--
 .../sis/storage/geoheif/UncompressedImage.java     |   9 +-
 .../sis/storage/geoheif/internal/Resources.java    | 301 +++++++++++++++++++++
 .../storage/geoheif/internal/Resources.properties  |  50 ++++
 .../sis/storage/geoheif/internal/Resources_en.java |  30 ++
 .../sis/storage/geoheif/internal/Resources_fr.java |  30 ++
 .../geoheif/internal/Resources_fr.properties       |  55 ++++
 .../sis/storage/geoheif/internal/package-info.java |  28 ++
 .../org/apache/sis/storage/isobmff/Reader.java     |  57 ++--
 .../isobmff/UnsupportedVersionException.java       |   3 +-
 .../apache/sis/storage/isobmff/base/ItemData.java  |   3 +-
 .../sis/storage/isobmff/base/ItemLocation.java     |   5 +-
 .../apache/sis/storage/isobmff/geo/ModelCRS.java   |   6 +-
 .../sis/storage/isobmff/mpeg/ComponentType.java    |   3 +-
 .../sis/storage/isobmff/mpeg/InterleavingMode.java |   3 +-
 .../isobmff/mpeg/UncompressedFrameConfig.java      |   4 +-
 21 files changed, 612 insertions(+), 80 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
index 2495ccd6e4..8e5dc79eec 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/IndexedResourceBundle.java
@@ -673,7 +673,8 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
 
     /**
      * Creates a new log record with the localized message identified by the 
given key.
-     * The logger name, source class and source method are initially {@code 
null}.
+     * The logger name is initialized to the name of the module of the 
resource bundle.
+     * The source class and source method are initially {@code null}.
      *
      * @param  level  the log record level.
      * @param  key    the resource key.
@@ -681,6 +682,7 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
      */
     public final LogRecord createLogRecord(final Level level, final short key) 
{
         final var record = new LogRecord(level, 
getKeyConstants().getKeyName(key));
+        record.setLoggerName(getClass().getModule().getName());
         record.setResourceBundleName(getClass().getName());
         record.setResourceBundle(this);
         return record;
@@ -688,7 +690,8 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
 
     /**
      * Creates a new log record with the localized message identified by the 
given key.
-     * The logger name, source class and source method are initially {@code 
null}.
+     * The logger name is initialized to the name of the module of the 
resource bundle.
+     * The source class and source method are initially {@code null}.
      *
      * @param  level  the log record level.
      * @param  key    the resource key.
@@ -705,7 +708,8 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
 
     /**
      * Creates a new log record with the localized message identified by the 
given key.
-     * The logger name, source class and source method are initially {@code 
null}.
+     * The logger name is initialized to the name of the module of the 
resource bundle.
+     * The source class and source method are initially {@code null}.
      *
      * @param  level  the log record level.
      * @param  key    the resource key.
@@ -722,7 +726,8 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
 
     /**
      * Creates a new log record with the localized message identified by the 
given key.
-     * The logger name, source class and source method are initially {@code 
null}.
+     * The logger name is initialized to the name of the module of the 
resource bundle.
+     * The source class and source method are initially {@code null}.
      *
      * @param  level  the log record level.
      * @param  key    the resource key.
@@ -741,7 +746,8 @@ public abstract class IndexedResourceBundle extends 
ResourceBundle implements Lo
 
     /**
      * Creates a new log record with the localized message identified by the 
given key.
-     * The logger name, source class and source method are initially {@code 
null}.
+     * The logger name is initialized to the name of the module of the 
resource bundle.
+     * The source class and source method are initially {@code null}.
      *
      * @param  level  the log record level.
      * @param  key    the resource key.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/FromImageIO.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/FromImageIO.java
index fa5bd332f4..ff0f9d01c4 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/FromImageIO.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/FromImageIO.java
@@ -30,6 +30,7 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.IllegalOpenParameterException;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.ByteRanges;
 import org.apache.sis.util.ArraysExt;
 
@@ -89,7 +90,7 @@ final class FromImageIO extends Image implements 
IIOReadWarningListener {
                 return provider;
             }
         }
-        throw new UnsupportedEncodingException("Could not find a " + format + 
" reader.");
+        throw new 
UnsupportedEncodingException(Resources.format(Resources.Keys.ImageReaderNotFound_1,
 format));
     }
 
     /**
@@ -109,7 +110,7 @@ final class FromImageIO extends Image implements 
IIOReadWarningListener {
         try {
             reader.setInput(input, true, true);
         } catch (IllegalArgumentException e) {
-            throw new IllegalOpenParameterException("Not an image input 
stream.", e);
+            throw new 
IllegalOpenParameterException(Resources.format(Resources.Keys.NotImageInputStream),
 e);
         }
     }
 
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
index b2fe0c42aa..4e5a86af63 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/GeoHeifStore.java
@@ -47,6 +47,7 @@ import org.apache.sis.storage.metadata.MetadataBuilder;
 import org.apache.sis.storage.base.URIDataStoreOption;
 import org.apache.sis.storage.modifier.CoverageModifier;
 import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.Root;
 import org.apache.sis.storage.isobmff.Reader;
 import org.apache.sis.util.collection.Containers;
@@ -318,11 +319,11 @@ public class GeoHeifStore extends DataStore implements 
Aggregate {
      * Logs a warning with a message built from localized resources. This 
method pretends that the
      * warning has been emitted by {@link #components()}. It should be the 
case, but often indirectly.
      *
-     * @param  errorKey  one of {@link Errors.Keys} values.
-     * @param  args   the parameter for the log message, which may be an array.
+     * @param  key   one of {@link Resources.Keys} values.
+     * @param  args  the parameter for the log message, which may be an array.
      */
-    final void warning(final short errorKey, final Object args) {
-        warning(Errors.forLocale(getLocale()).createLogRecord(Level.WARNING, 
errorKey, args));
+    final void warning(final short key, final Object args) {
+        
warning(Resources.forLocale(getLocale()).createLogRecord(Level.WARNING, key, 
args));
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
index 22475e7a0a..544db81dc1 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
@@ -31,6 +31,7 @@ import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.modifier.CoverageModifier;
 import org.apache.sis.storage.isobmff.mpeg.Component;
 import org.apache.sis.storage.isobmff.mpeg.ComponentType;
@@ -142,7 +143,7 @@ final class ImageModel {
                 if (dataType == null) {
                     dataType = c.getDataType();
                 } else if (dataType != c.getDataType()) {
-                    throw new DataStoreContentException("All bands shall be of 
the same data type.");
+                    throw new 
DataStoreContentException(Resources.format(Resources.Keys.InconsistentBandDataType));
                 }
                 bitDepth = Short.toUnsignedInt(c.bitDepth);
                 /*
@@ -238,7 +239,8 @@ final class ImageModel {
             switch (interleaveType) {
                 case COMPONENT: isBanded = true;  break;    // Java2D: 
BandedSampleModel
                 case PIXEL:     isBanded = false; break;    // Java2D: 
PixelInterleavedSampleModel
-                default: throw new UnsupportedEncodingException("Unsupported 
interleave type: " + interleaveType);
+                default: throw new UnsupportedEncodingException(
+                            
Resources.format(Resources.Keys.UnsupportedInterleave_1, interleaveType));
             }
             sampleModel = new SampleModelBuilder(dataType, tileSize, 
bitsPerSample, isBanded).build();
         } else {
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResourceBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResourceBuilder.java
index 73a0e31e92..33251f7b1d 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResourceBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResourceBuilder.java
@@ -26,7 +26,6 @@ import java.util.LinkedHashMap;
 import java.util.Collection;
 import java.util.StringJoiner;
 import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import java.io.IOException;
 import java.nio.ByteOrder;
 import java.awt.Dimension;
@@ -57,9 +56,9 @@ import 
org.apache.sis.storage.isobmff.mpeg.CompressedUnitsItemInfo;
 import org.apache.sis.storage.isobmff.mpeg.CompressionConfiguration;
 import org.apache.sis.storage.isobmff.mpeg.UncompressedFrameConfig;
 import org.apache.sis.storage.isobmff.mpeg.UnitType;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Emptiable;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.pending.jdk.JDK18;
 
 
@@ -315,7 +314,7 @@ final class ImageResourceBuilder implements Emptiable {
             if (duplicated) {
                 final String type = Box.formatFourCC(property.type());
                 if (duplicatedBoxes.add(type)) {
-                    store().warning(Errors.Keys.DuplicatedElement_1, type);
+                    store().warning(Resources.Keys.DuplicatedBox_1, type);
                 }
             }
         }
@@ -358,7 +357,14 @@ final class ImageResourceBuilder implements Emptiable {
                 return units[0];
             }
         }
-        throw new UnsupportedEncodingException("Unsupported compression.");
+        throw new 
UnsupportedEncodingException(resources().getString(Resources.Keys.UnsupportedCompression));
+    }
+
+    /**
+     * Returns the resources for localized warnings or error messages.
+     */
+    private Resources resources() {
+        return Resources.forLocale(store().getLocale());
     }
 
     /**
@@ -374,18 +380,16 @@ final class ImageResourceBuilder implements Emptiable {
         boolean essential = false;
         if (!unknownBoxes.isEmpty()) {
             final Level level;
-            final var message = new StringBuilder();
+            final short message;
             final Collection<Boolean> essentials = unknownBoxes.values();
             if (essentials.contains(Boolean.TRUE)) {
                 essentials.removeIf((e) -> !e);  // Remove all non-essential 
boxes.
-                message.append("Cannot create a resource for \"").append(name)
-                        .append("\" because the following essential boxes are 
not handled: ");
-                level = Level.WARNING;
                 essential = true;
+                level     = Level.WARNING;
+                message   = Resources.Keys.EssentialBoxesIgnored_2;
             } else {
-                message.append("The \"").append(name)
-                        .append("\" resource has been read but the following 
boxes have been ignored: ");
-                level = Level.FINE;
+                level   = Level.FINE;
+                message = Resources.Keys.OptionalBoxesIgnored_2;
             }
             final var sj = new StringJoiner(", ");
             for (Object id : unknownBoxes.keySet()) {
@@ -396,8 +400,7 @@ final class ImageResourceBuilder implements Emptiable {
                 }
                 sj.add(id.toString());
             }
-            final var record = new LogRecord(level, 
message.append(sj).append('.').toString());
-            store().warning(record);
+            store().warning(resources().createLogRecord(level, message, name, 
sj));
         }
         return essential;
     }
@@ -586,7 +589,7 @@ final class ImageResourceBuilder implements Emptiable {
         if (sampleModel != null) {
             return sampleModel;
         }
-        throw new DataStoreContentException("Unspecified sample model.");
+        throw new 
DataStoreContentException(resources().getString(Resources.Keys.UnspecifiedSampleModel));
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
index 4a95c20fb3..3d572ed8e9 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ResourceBuilder.java
@@ -27,8 +27,6 @@ import java.util.LinkedHashMap;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.stream.Stream;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import java.io.IOException;
 import javax.imageio.spi.ImageReaderSpi;
 import org.opengis.util.GenericName;
@@ -37,6 +35,7 @@ import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.Box;
 import org.apache.sis.storage.isobmff.ByteRanges;
 import org.apache.sis.storage.isobmff.Root;
@@ -221,8 +220,8 @@ final class ResourceBuilder {
             case ItemLocation.BOXTYPE: {
                 for (final ItemLocation.Item item : ((ItemLocation) 
box).items) {
                     if (itemLocations.putIfAbsent(item.itemID, item) != null) {
-                        warning("Many locations found for the \"{0}\" 
resource.",
-                                getResourceName(item.itemID, 
Vocabulary.Keys.Item_1));
+                        
store.warning(Resources.Keys.ManyLocationsForResource_1,
+                                      getResourceName(item.itemID, 
Vocabulary.Keys.Item_1));
                     }
                 }
                 break;
@@ -246,17 +245,6 @@ final class ResourceBuilder {
         }
     }
 
-    /**
-     * Logs a warning as if it was emitted by {@link 
GeoHeifStore#components()}.
-     *
-     * @param  message  the message with a "{0}" pattern to be replaced by the 
resource name.
-     * @param  name     the resource name.
-     */
-    private void warning(String message, final CharSequence name) {
-        message = message.replace("{0}", name);
-        store.warning(new LogRecord(Level.WARNING, message));
-    }
-
     /**
      * Returns the provider of <abbr>JPEG</abbr> readers.
      */
@@ -360,7 +348,8 @@ final class ResourceBuilder {
         try {
             createImage(itemID, info(itemInfos.remove(itemID)), null);
         } catch (UnsupportedEncodingException e) {
-            store.listeners().warning("A resource uses an unsupported sample 
model.", e);
+            store.warning(Resources.Keys.UnsupportedSampleModel_1,
+                          getResourceName(itemID, Vocabulary.Keys.Item_1));
         }
     }
 
@@ -388,7 +377,7 @@ final class ResourceBuilder {
         for (final ItemInfoEntry entry : info) {
             final CharSequence name = getResourceName(entry);
             if (entry.itemProtectionIndex != 0) {
-                warning("The \"{0}\" resource is protected.", name);
+                store.warning(Resources.Keys.ResourceIsProtected_1, name);
                 continue;
             }
             final int imageIndex;
@@ -406,7 +395,7 @@ final class ResourceBuilder {
                 continue;
             }
             if (coverage.isEmpty()) {
-                warning("The \"{0}\" resource is empty.", name);
+                store.warning(Resources.Keys.ResourceIsEmpty_1, name);
                 continue;
             }
             if (firstBuilder == null) {
@@ -415,7 +404,8 @@ final class ResourceBuilder {
             Image image = null;
             switch (entry.itemType) {
                 default: {
-                    warning("Unsupported type " + 
Box.formatFourCC(entry.itemType) + " for the \"{0}\" resource.", name);
+                    store.warning(Resources.Keys.UnsupportedResourceType_2,
+                                  new CharSequence[] {name, 
Box.formatFourCC(entry.itemType)});
                     continue;
                 }
                 /*
@@ -438,7 +428,7 @@ final class ResourceBuilder {
                         }
                         if (addTo == null && tiles != null && 
!tiles.isEmpty()) {
                             builders.remove(itemProperties);    // Builder 
cannot be reused after resource creation.
-                            resources(entry.itemID).add(coverage.build(name, 
tiles));
+                            
getResources(entry.itemID).add(coverage.build(name, tiles));
                         }
                     }
                     continue;
@@ -483,13 +473,13 @@ final class ResourceBuilder {
                 }
             }
             if (image == null) {
-                warning("No data found for the \"{0}\" resource.", name);
+                store.warning(Resources.Keys.NoDataFoundForResource_1, name);
             } else {
                 if (addTo != null) {
                     addTo.add(image);
                 } else {
                     builders.remove(itemProperties);    // Builder cannot be 
reused after resource creation.
-                    resources(entry.itemID).add(coverage.build(name, image));
+                    getResources(entry.itemID).add(coverage.build(name, 
image));
                 }
             }
         }
@@ -562,7 +552,7 @@ final class ResourceBuilder {
                             }
                         }
                     }
-                    resources(group.groupID).add(resource);
+                    getResources(group.groupID).add(resource);
                 }
             }
         }
@@ -580,7 +570,7 @@ final class ResourceBuilder {
      * @param  itemID  item identifier for which to get the resources.
      * @return modifiable list of resources for the given identifier.
      */
-    private List<Resource> resources(final int itemID) {
+    private List<Resource> getResources(final int itemID) {
         return itemResources.computeIfAbsent(itemID, (key) -> new 
ArrayList<>());
     }
 }
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
index 4575a308a6..a6bb46e40a 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/UncompressedImage.java
@@ -37,6 +37,7 @@ import org.apache.sis.storage.UnsupportedEncodingException;
 import org.apache.sis.storage.isobmff.ByteRanges;
 import org.apache.sis.storage.isobmff.mpeg.CompressedUnitsItemInfo;
 import org.apache.sis.storage.isobmff.mpeg.CompressionConfiguration;
+import org.apache.sis.storage.geoheif.internal.Resources;
 
 
 /**
@@ -112,9 +113,9 @@ class UncompressedImage extends Image {
             case 0: return null;
             case CompressionConfiguration.COMPRESSION_ZLIB:    return new 
Deflate(input, listeners, false);
             case CompressionConfiguration.COMPRESSION_DEFLATE: return new 
Deflate(input, listeners, true);
-            default: throw new UnsupportedEncodingException("The \"" +
-                    CompressionConfiguration.formatFourCC(compressionType) + 
"\" compression is not supported.");
-
+            default: throw new UnsupportedEncodingException(Resources.format(
+                        Resources.Keys.UnsupportedCompression_1,
+                        
CompressionConfiguration.formatFourCC(compressionType)));
         }
     }
 
@@ -147,7 +148,7 @@ class UncompressedImage extends Image {
      */
     protected CompressedUnitsItemInfo.Unit compressedImageUnit(final long 
tileIndex) throws DataStoreException {
         if (compressedImageUnit == null) {
-            throw new DataStoreContentException("Missing compressed unit.");
+            throw new 
DataStoreContentException(Resources.format(Resources.Keys.MissingCompressedUnit));
         }
         return compressedImageUnit;
     }
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.java
new file mode 100644
index 0000000000..b1fa39022e
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geoheif.internal;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import org.apache.sis.util.resources.KeyConstants;
+import org.apache.sis.util.resources.IndexedResourceBundle;
+
+
+/**
+ * Warning and error messages that are specific to the GeoHEIF module.
+ * Resources in this file should not be used by any other module.
+ * For resources shared by many modules in the Apache <abbr>SIS</abbr> project,
+ * see the {@code org.apache.sis.util.resources} package.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ */
+public class Resources extends IndexedResourceBundle {
+    /**
+     * Resource keys. This class is used when compiling sources, but no 
dependencies to
+     * {@code Keys} should appear in any resulting class files. Since the Java 
compiler
+     * inlines final integer values, using long identifiers will not bloat the 
constant
+     * pools of compiled classes.
+     *
+     * @author  Martin Desruisseaux (IRD, Geomatys)
+     */
+    public static final class Keys extends KeyConstants {
+        /**
+         * The unique instance of key constants handler.
+         */
+        static final Keys INSTANCE = new Keys();
+
+        /**
+         * For {@link #INSTANCE} creation only.
+         */
+        private Keys() {
+        }
+
+        /**
+         * Returns the value of a field declared in this {@code Keys} class.
+         * This method is needed for encapsulation reason, because classes in
+         * other modules cannot access this class even by reflection.
+         */
+        @Override
+        protected Object getStaticValue(final Field field) throws 
IllegalAccessException {
+            if (field.getDeclaringClass() == Keys.class) {
+                return field.get(null);
+            }
+            throw new IllegalAccessException();
+        }
+
+        /**
+         * The ‘{0}’ box is longer than expected.
+         */
+        public static final short BoxLongerThanExpected_1 = 1;
+
+        /**
+         * Cannot decode the Coordinate Reference System.
+         */
+        public static final short CannotDecodeCRS = 2;
+
+        /**
+         * Cannot read the ‘{0}’ box.
+         */
+        public static final short CannotReadBox_1 = 3;
+
+        /**
+         * The ‘{0}’ box appears more than once.
+         */
+        public static final short DuplicatedBox_1 = 4;
+
+        /**
+         * Cannot create the “{0}” resource because the following essential 
boxes are not handled: {1}.
+         */
+        public static final short EssentialBoxesIgnored_2 = 5;
+
+        /**
+         * Container box ‘{0}’ cannot contain elements of type ‘{1}’.
+         */
+        public static final short IllegalChildForBox_2 = 6;
+
+        /**
+         * Cannot find a {0} image reader.
+         */
+        public static final short ImageReaderNotFound_1 = 7;
+
+        /**
+         * All bands shall be of the same data type.
+         */
+        public static final short InconsistentBandDataType = 8;
+
+        /**
+         * Malformed HEIF file because of invalid box size ({0} bytes).
+         */
+        public static final short InvalidBoxSize_1 = 9;
+
+        /**
+         * Many locations have been found for the “{0}” resource.
+         */
+        public static final short ManyLocationsForResource_1 = 10;
+
+        /**
+         * Missing unit of compressed data.
+         */
+        public static final short MissingCompressedUnit = 11;
+
+        /**
+         * No data has been found for the “{0}” resource.
+         */
+        public static final short NoDataFoundForResource_1 = 12;
+
+        /**
+         * Not an image input stream.
+         */
+        public static final short NotImageInputStream = 13;
+
+        /**
+         * The “{0}” resource has been created but the following boxes have 
been ignored: {1}.
+         */
+        public static final short OptionalBoxesIgnored_2 = 14;
+
+        /**
+         * The “{0}” resource is empty.
+         */
+        public static final short ResourceIsEmpty_1 = 15;
+
+        /**
+         * The “{0}” resource is protected.
+         */
+        public static final short ResourceIsProtected_1 = 16;
+
+        /**
+         * Stream has an unknown length.
+         */
+        public static final short StreamOfUnknownLength = 17;
+
+        /**
+         * Unexpected construction method.
+         */
+        public static final short UnexpectedConstructionMethod = 18;
+
+        /**
+         * The sample model of the image is unspecified.
+         */
+        public static final short UnspecifiedSampleModel = 19;
+
+        /**
+         * The ‘{0}’ type of box is unsupported.
+         */
+        public static final short UnsupportedBoxType_1 = 20;
+
+        /**
+         * The ‘{0}’ image component type is unsupported.
+         */
+        public static final short UnsupportedComponent_1 = 21;
+
+        /**
+         * This compression is unsupported.
+         */
+        public static final short UnsupportedCompression = 22;
+
+        /**
+         * The ‘{0}’ compression is unsupported.
+         */
+        public static final short UnsupportedCompression_1 = 23;
+
+        /**
+         * The ‘{0}’ reference system encoding is unsupported.
+         */
+        public static final short UnsupportedCrsEncoding_1 = 24;
+
+        /**
+         * The ‘{0}’ image profile is unsupported.
+         */
+        public static final short UnsupportedImageProfile_1 = 25;
+
+        /**
+         * The ‘{0}’ interleave type is unsupported.
+         */
+        public static final short UnsupportedInterleave_1 = 26;
+
+        /**
+         * Unsupported type ‘{1}’ for the “{0}” resource.
+         */
+        public static final short UnsupportedResourceType_2 = 27;
+
+        /**
+         * Resource “{0}” uses an unsupported image model.
+         */
+        public static final short UnsupportedSampleModel_1 = 28;
+
+        /**
+         * Version {1} of the ‘{0}’ box is unsupported.
+         */
+        public static final short UnsupportedVersion_2 = 29;
+    }
+
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources() {
+    }
+
+    /**
+     * Opens the binary file containing the localized resources to load.
+     * This method delegates to {@link Class#getResourceAsStream(String)},
+     * but this delegation must be done from the same module as the one
+     * that provides the binary file.
+     */
+    @Override
+    protected InputStream getResourceAsStream(final String name) {
+        return getClass().getResourceAsStream(name);
+    }
+
+    /**
+     * Returns the handle for the {@code Keys} constants.
+     *
+     * @return a handler for the constants declared in the inner {@code Keys} 
class.
+     */
+    @Override
+    protected KeyConstants getKeyConstants() {
+        return Keys.INSTANCE;
+    }
+
+    /**
+     * Returns resources in the given locale.
+     *
+     * @param  locale  the locale, or {@code null} for the default locale.
+     * @return resources in the given locale.
+     * @throws MissingResourceException if resources cannot be found.
+     */
+    public static Resources forLocale(final Locale locale) {
+        /*
+         * We cannot factorize this method into the parent class, because we 
need to call
+         * `ResourceBundle.getBundle(String)` from the module that provides 
the resources.
+         * We do not cache the result because `ResourceBundle` already 
provides a cache.
+         */
+        return (Resources) getBundle(Resources.class.getName(), 
nonNull(locale));
+    }
+
+    /**
+     * Gets a string for the given key from this resource bundle or one of its 
parents.
+     *
+     * @param  key  the key for the desired string.
+     * @return the string for the given key.
+     * @throws MissingResourceException if no object for the given key can be 
found.
+     */
+    public static String format(final short key) {
+        return forLocale(null).getString(key);
+    }
+
+    /**
+     * Gets a string for the given key and replaces all occurrence of "{0}"
+     * with value of {@code arg0}.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be 
found.
+     */
+    public static String format(final short  key,
+                                final Object arg0)
+    {
+        return forLocale(null).getString(key, arg0);
+    }
+
+    /**
+     * Gets a string for the given key and replaces all occurrence of "{0}",
+     * "{1}", with values of {@code arg0}, {@code arg1}.
+     *
+     * @param  key   the key for the desired string.
+     * @param  arg0  value to substitute to "{0}".
+     * @param  arg1  value to substitute to "{1}".
+     * @return the formatted string for the given key.
+     * @throws MissingResourceException if no object for the given key can be 
found.
+     */
+    public static String format(final short  key,
+                                final Object arg0,
+                                final Object arg1) throws 
MissingResourceException
+    {
+        return forLocale(null).getString(key, arg0, arg1);
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.properties
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.properties
new file mode 100644
index 0000000000..871e48f13b
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources.properties
@@ -0,0 +1,50 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Resources in this file are for "sis-geotiff" usage only and should not be 
used by any other module.
+# For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
+#
+BoxLongerThanExpected_1      = The \u2018{0}\u2019 box is longer than expected.
+CannotDecodeCRS              = Cannot decode the Coordinate Reference System.
+CannotReadBox_1              = Cannot read the \u2018{0}\u2019 box.
+DuplicatedBox_1              = The \u2018{0}\u2019 box appears more than once.
+EssentialBoxesIgnored_2      = Cannot create the \u201c{0}\u201d resource 
because the following essential boxes are not handled: {1}.
+OptionalBoxesIgnored_2       = The \u201c{0}\u201d resource has been created 
but the following boxes have been ignored: {1}.
+IllegalChildForBox_2         = Container box \u2018{0}\u2019 cannot contain 
elements of type \u2018{1}\u2019.
+ImageReaderNotFound_1        = Cannot find a {0} image reader.
+InconsistentBandDataType     = All bands shall be of the same data type.
+InvalidBoxSize_1             = Malformed HEIF file because of invalid box size 
({0} bytes).
+ManyLocationsForResource_1   = Many locations have been found for the 
\u201c{0}\u201d resource.
+MissingCompressedUnit        = Missing unit of compressed data.
+NoDataFoundForResource_1     = No data has been found for the \u201c{0}\u201d 
resource.
+NotImageInputStream          = Not an image input stream.
+ResourceIsEmpty_1            = The \u201c{0}\u201d resource is empty.
+ResourceIsProtected_1        = The \u201c{0}\u201d resource is protected.
+StreamOfUnknownLength        = Stream has an unknown length.
+UnexpectedConstructionMethod = Unexpected construction method.
+UnspecifiedSampleModel       = The sample model of the image is unspecified.
+UnsupportedBoxType_1         = The \u2018{0}\u2019 type of box is unsupported.
+UnsupportedComponent_1       = The \u2018{0}\u2019 image component type is 
unsupported.
+UnsupportedCompression       = This compression is unsupported.
+UnsupportedCompression_1     = The \u2018{0}\u2019 compression is unsupported.
+UnsupportedCrsEncoding_1     = The \u2018{0}\u2019 reference system encoding 
is unsupported.
+UnsupportedImageProfile_1    = The \u2018{0}\u2019 image profile is 
unsupported.
+UnsupportedInterleave_1      = The \u2018{0}\u2019 interleave type is 
unsupported.
+UnsupportedSampleModel_1     = Resource \u201c{0}\u201d uses an unsupported 
image model.
+UnsupportedResourceType_2    = Unsupported type \u2018{1}\u2019 for the 
\u201c{0}\u201d resource.
+UnsupportedVersion_2         = Version {1} of the \u2018{0}\u2019 box is 
unsupported.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_en.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_en.java
new file mode 100644
index 0000000000..5aedf88722
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_en.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geoheif.internal;
+
+
+/**
+ * Resource in English language.
+ */
+public class Resources_en extends Resources {
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources_en() {
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.java
new file mode 100644
index 0000000000..17821791c3
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geoheif.internal;
+
+
+/**
+ * Messages in French language.
+ */
+public class Resources_fr extends Resources {
+    /**
+     * Constructs a new resource bundle loading data from
+     * the resource file of the same name as this class.
+     */
+    public Resources_fr() {
+    }
+}
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.properties
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.properties
new file mode 100644
index 0000000000..e7af2ad72c
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/Resources_fr.properties
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Resources in this file are for "sis-geotiff" usage only and should not be 
used by any other module.
+# For resources shared by all modules in the Apache SIS project, see 
"org.apache.sis.util.resources" package.
+#
+# Punctuation rules in French (source: 
http://unicode.org/udhr/n/notes_fra.html)
+#
+#   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
+#   U+00A0 NO-BREAK SPACE         before  :
+#
+BoxLongerThanExpected_1      = La bo\u00eete \u2018{0}\u2019 est plus longue 
qu\u2019attendue.
+CannotDecodeCRS              = Ne peut pas d\u00e9coder le syst\u00e8me de 
r\u00e9f\u00e9rence des coordonn\u00e9es.
+CannotReadBox_1              = Ne peut pas lire la bo\u00eete \u2018{0}\u2019.
+DuplicatedBox_1              = La bo\u00eete \u2018{0}\u2019 appara\u00eet 
plus d\u2019une fois.
+EssentialBoxesIgnored_2      = Ne peut pas cr\u00e9er la ressource 
\u201c{0}\u201d parce que les bo\u00eetes essentielles suivantes ne sont pas 
g\u00e9r\u00e9es: {1}.
+OptionalBoxesIgnored_2       = La ressource \u00ab\u202f{0}\u202f\u00bb a 
\u00e9t\u00e9 lue mais les bo\u00eetes suivantes ont \u00e9t\u00e9 
ignor\u00e9es: {1}.
+IllegalChildForBox_2         = Le contenant \u2018{0}\u2019 ne peut pas 
contenir des \u00e9l\u00e9ments de type \u2018{1}\u2019.
+ImageReaderNotFound_1        = Ne peut pas trouver un lecteur d\u2019image {0}.
+InconsistentBandDataType     = Toutes les bandes doivent \u00eatre du 
m\u00eame type.
+InvalidBoxSize_1             = Le fichier HEIF est mal form\u00e9 car il 
contient une bo\u00eete de longueur invalide ({0} bytes).
+ManyLocationsForResource_1   = Plusieurs positions ont \u00e9t\u00e9 
trouv\u00e9es pour la ressource \u00ab\u202f{0}\u202f\u00bb.
+MissingCompressedUnit        = Il manque une unit\u00e9 de donn\u00e9es 
compress\u00e9es.
+NoDataFoundForResource_1     = Aucune donn\u00e9es n\u2019a \u00e9t\u00e9 
trouv\u00e9e pour la ressource \u00ab\u202f{0}\u202f\u00bb.
+NotImageInputStream          = Ce n\u2019est pas un flux de donn\u00e9es 
d\u2019image.
+ResourceIsEmpty_1            = La ressource \u00ab\u202f{0}\u202f\u00bb est 
vide.
+ResourceIsProtected_1        = La ressource \u00ab\u202f{0}\u202f\u00bb est 
prot\u00e9g\u00e9e.
+StreamOfUnknownLength        = Le flux de donn\u00e9es a une longueur inconnue.
+UnexpectedConstructionMethod = M\u00e9thode de construction innatendue.
+UnspecifiedSampleModel       = Le mod\u00e8le de l\u2019image n\u2019a pas 
\u00e9t\u00e9 sp\u00e9cifi\u00e9e.
+UnsupportedBoxType_1         = Le type de bo\u00eete \u2018{0}\u2019 
n\u2019est pas support\u00e9.
+UnsupportedComponent_1       = le type de composant d\u2019image 
\u2018{0}\u2019 n\u2019est pas support\u00e9.
+UnsupportedCompression       = Cette compression n\u2019est pas support\u00e9e.
+UnsupportedCompression_1     = La compression \u2018{0}\u2019 n\u2019est pas 
support\u00e9e.
+UnsupportedCrsEncoding_1     = L\u2019encodage \u2018{0}\u2019 pour les 
syst\u00e8mes de r\u00e9f\u00e9rence n\u2019est pas support\u00e9.
+UnsupportedImageProfile_1    = Le profil d\u2019image \u2018{0}\u2019 
n\u2019est pas support\u00e9.
+UnsupportedInterleave_1      = Le type d\u2019entrelacement \u2018{0}\u2019 
n\u2019est pas support\u00e9.
+UnsupportedSampleModel_1     = La ressource \u00ab\u202f{0}\u202f\u00bb 
utilise un mod\u00e8le d\u2019image qui n\u2019est pas support\u00e9.
+UnsupportedResourceType_2    = Le type \u2018{1}\u2019 n\u2019est pas 
support\u00e9 pour la ressource \u00ab\u202f{0}\u202f\u00bb.
+UnsupportedVersion_2         = La version {1} de la bo\u00eete \u2018{0}\u2019 
n\u2019est pas support\u00e9e.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/package-info.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/package-info.java
new file mode 100644
index 0000000000..fbf148cc81
--- /dev/null
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/internal/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Shared classes for the implementation of GeoHEIF reader.
+ *
+ * <STRONG>Do not use!</STRONG>
+ *
+ * This package is for internal use by Apache <abbr>SIS</abbr> only.
+ * Classes in this package may change in incompatible ways in any future 
version without notice.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+package org.apache.sis.storage.geoheif.internal;
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/Reader.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/Reader.java
index 3e66f51870..4e6fd5044d 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/Reader.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/Reader.java
@@ -34,6 +34,7 @@ import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.base.MovieHeader;
 import org.apache.sis.storage.isobmff.mpeg.ComponentDefinition;
 import org.apache.sis.util.ArraysExt;
@@ -190,7 +191,7 @@ public final class Reader implements Cloneable {
                 min += Long.BYTES;
             }
             if (end < min) {
-                throw new DataStoreContentException("Malformed HEIF file: 
invalid box size: " + end + " bytes");
+                throw new 
DataStoreContentException(resources().getString(Resources.Keys.InvalidBoxSize_1,
 end));
             }
             end = Math.addExact(startOfCurrentBox, end);    // Now, it becomes 
the real box end
         }
@@ -207,15 +208,15 @@ public final class Reader implements Cloneable {
         } catch (DataStoreException cause) {
             box = null;
             if (isNewWarning(type, Classes.getClass(cause))) {
-                var record = new LogRecord(Level.WARNING, "Cannot read the “" 
+ Box.formatFourCC(type) + "” box.");
-                record.setLoggerName(LOGGER_NAME);
+                var record = resources().createLogRecord(Level.WARNING, 
Resources.Keys.CannotReadBox_1, Box.formatFourCC(type));
                 record.setThrown(cause);
-                listeners.warning(record);
+                warning(record);
             }
         }
         endOfCurrentBox = end;      // May have been modified by recursive 
invocations.
         if (end >= 0 && input.getStreamPosition() > end) {
-            throw new DataStoreContentException("The \"" + 
Box.formatFourCC(box.type()) + "\" box is longer than expected.");
+            throw new DataStoreContentException(resources().getString(
+                    Resources.Keys.BoxLongerThanExpected_1, 
Box.formatFourCC(box.type())));
         }
         return box;
     }
@@ -262,7 +263,7 @@ public final class Reader implements Cloneable {
      */
     public final int[] readRemainingInts() throws IOException {
         if (endOfCurrentBox < 0) {
-            throw new IOException("Stream of unknown length.");
+            throw new 
IOException(resources().getString(Resources.Keys.StreamOfUnknownLength));
         }
         int n = Math.toIntExact((endOfCurrentBox - input.getStreamPosition()) 
/ Integer.BYTES);
         return (n != 0) ? input.readInts(n) : ArraysExt.EMPTY_INT;
@@ -346,9 +347,7 @@ public final class Reader implements Cloneable {
             if (type instanceof Integer fourCC) {
                 type = Box.formatFourCC(fourCC);
             }
-            var record = new LogRecord(Level.WARNING, "The “" + type + "” type 
of box is unrecognized.");
-            record.setLoggerName(LOGGER_NAME);
-            listeners.warning(record);
+            warning(Resources.Keys.UnsupportedBoxType_1, type);
         }
     }
 
@@ -362,9 +361,11 @@ public final class Reader implements Cloneable {
      */
     public final void unexpectedChildType(final int container, final int 
child) {
         if (isNewWarning(container, child)) {
-            final var message = new StringBuilder("Container box 
“").append(Box.formatFourCC(container))
-                    .append("” cannot contain children of type 
“").append(Box.formatFourCC(child)).append("”.");
-            listeners.warning(message.toString());
+            warning(Resources.Keys.IllegalChildForBox_2,
+                    new String[] {
+                        Box.formatFourCC(container),
+                        Box.formatFourCC(child)
+                    });
         }
     }
 
@@ -377,14 +378,38 @@ public final class Reader implements Cloneable {
      */
     public final void cannotParse(final Exception error, final String value, 
final boolean ignoreable) {
         if (isNewWarning(error.getClass(), value)) {
-            final LogRecord record = Errors.forLocale(listeners.getLocale())
-                    .createLogRecord(ignoreable ? Level.FINE : Level.WARNING, 
Errors.Keys.CanNotParse_1, value);
-            record.setLoggerName(LOGGER_NAME);
+            final LogRecord record = resources().createLogRecord(
+                    ignoreable ? Level.FINE : Level.WARNING, 
Errors.Keys.CanNotParse_1, value);
             record.setThrown(error);
-            listeners.warning(record);
+            warning(record);
         }
     }
 
+    /**
+     * Returns the resources for localized warnings or error messages.
+     */
+    private Resources resources() {
+        return Resources.forLocale(listeners.getLocale());
+    }
+
+    /**
+     * Logs a warning with a message built from localized resources.
+     *
+     * @param  key   one of {@link Resources.Keys} values.
+     * @param  args  the parameter for the log message, which may be an array.
+     */
+    private void warning(final short key, final Object args) {
+        warning(resources().createLogRecord(Level.WARNING, key, args));
+    }
+
+    /**
+     * Logs the given warning.
+     */
+    private void warning(final LogRecord record) {
+        record.setLoggerName(LOGGER_NAME);
+        listeners.warning(record);
+    }
+
     /**
      * Returns a clone of this reader. Used for inheriting all the context
      * and making changes to it without impacting the parent context.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/UnsupportedVersionException.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/UnsupportedVersionException.java
index 5be1c8b015..b20c2bfc22 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/UnsupportedVersionException.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/UnsupportedVersionException.java
@@ -17,6 +17,7 @@
 package org.apache.sis.storage.isobmff;
 
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 
 
 /**
@@ -39,6 +40,6 @@ public final class UnsupportedVersionException extends 
UnsupportedEncodingExcept
      * @param  version  the unsupported version.
      */
     public UnsupportedVersionException(final int type, final int version) {
-        super("Version " + version + " of '" + Box.formatFourCC(type) + "' 
boxes is unsupported.");
+        super(Resources.format(Resources.Keys.UnsupportedVersion_2, 
Box.formatFourCC(type), version));
     }
 }
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
index 4432a05528..a9142bb1da 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemData.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.isobmff.base;
 import java.io.IOException;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.ByteRanges;
 import org.apache.sis.storage.isobmff.Reader;
 import org.apache.sis.storage.isobmff.Box;
@@ -92,7 +93,7 @@ public class ItemData extends Box implements 
ByteRanges.Reader {
                 length = size;
             }
         } else if (length < 0) {
-            throw new DataStoreContentException("Stream of unknown length.");
+            throw new 
DataStoreContentException(Resources.format(Resources.Keys.StreamOfUnknownLength));
         }
         offset = Math.addExact(payloadOffset, offset);
         addTo.addRange(offset, Math.addExact(offset, length));
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemLocation.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemLocation.java
index b4974caea4..10d9b000b2 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemLocation.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/base/ItemLocation.java
@@ -22,6 +22,7 @@ import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.UnsupportedVersionException;
 import org.apache.sis.storage.isobmff.VectorReader;
 import org.apache.sis.storage.isobmff.ByteRanges;
@@ -249,7 +250,7 @@ public final class ItemLocation extends FullBox {
         public void resolve(long offset, long length, ByteRanges addTo) throws 
DataStoreException {
             switch (constructionMethod) {
                 default: {
-                    throw new DataStoreContentException("Unexpected 
construction method.");
+                    throw new 
DataStoreContentException(Resources.format(Resources.Keys.UnexpectedConstructionMethod));
                 }
                 case IDAT_OFFSET:
                 case ITEM_OFFSET: {
@@ -269,7 +270,7 @@ public final class ItemLocation extends FullBox {
                         } else if (length >= 0) {
                             available = length;
                         } else {
-                            throw new DataStoreException("Stream of unknown 
length.");
+                            throw new 
DataStoreException(Resources.format(Resources.Keys.StreamOfUnknownLength));
                         }
                         if (available < offset) {
                             offset -= available;    // Make offset relative to 
the next extent.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/ModelCRS.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/ModelCRS.java
index 82c19b5b6f..6ea96ebc80 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/ModelCRS.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/geo/ModelCRS.java
@@ -25,6 +25,7 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.FullBox;
 import org.apache.sis.storage.isobmff.Reader;
 import org.apache.sis.storage.isobmff.UnsupportedVersionException;
@@ -135,12 +136,13 @@ public final class ModelCRS extends FullBox {
                     case CRSU: return CRS.forCode(crs);
                     case WKT2: return CRS.fromWKT(crs);
                     default: {
-                        warning = new LogRecord(Level.WARNING, "Unknown CRS 
encoding: “" + formatFourCC(crsEncoding) + "”.");
+                        warning = Resources.forLocale(null).createLogRecord(
+                                Level.WARNING, 
Resources.Keys.UnsupportedCrsEncoding_1, formatFourCC(crsEncoding));
                         break;
                     }
                 }
             } catch (FactoryException e) {
-                warning = new LogRecord(Level.WARNING, "Cannot decode the 
CRS.");
+                warning = 
Resources.forLocale(null).createLogRecord(Level.WARNING, 
Resources.Keys.CannotDecodeCRS);
                 warning.setThrown(e);
             }
             warning.setLoggerName(Reader.LOGGER_NAME);
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/ComponentType.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/ComponentType.java
index 9ec5382712..1095b94203 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/ComponentType.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/ComponentType.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.isobmff.mpeg;
 import java.awt.color.ColorSpace;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 
 
 /**
@@ -146,7 +147,7 @@ public enum ComponentType {
         if (ordinal >= 0 && ordinal < VALUES.length) {
             return VALUES[ordinal];
         }
-        throw new UnsupportedEncodingException("Unknown interleaving mode: " + 
ordinal);
+        throw new 
UnsupportedEncodingException(Resources.format(Resources.Keys.UnsupportedComponent_1,
 ordinal));
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/InterleavingMode.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/InterleavingMode.java
index 5499f081c3..2fce81baf8 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/InterleavingMode.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/InterleavingMode.java
@@ -20,6 +20,7 @@ import java.awt.image.BandedSampleModel;
 import java.awt.image.ComponentSampleModel;
 import java.awt.image.PixelInterleavedSampleModel;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.util.CharSequences;
 
 
@@ -84,7 +85,7 @@ public enum InterleavingMode {
         if (ordinal >= 0 && ordinal < VALUES.length) {
             return VALUES[ordinal];
         }
-        throw new UnsupportedEncodingException("Unknown interleaving mode: " + 
ordinal);
+        throw new 
UnsupportedEncodingException(Resources.format(Resources.Keys.UnsupportedInterleave_1,
 ordinal));
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/UncompressedFrameConfig.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/UncompressedFrameConfig.java
index b939c4bd93..edaa4b5a1e 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/UncompressedFrameConfig.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/UncompressedFrameConfig.java
@@ -21,6 +21,7 @@ import java.awt.color.ColorSpace;
 import java.awt.image.SinglePixelPackedSampleModel;
 import org.apache.sis.io.stream.ChannelDataInput;
 import org.apache.sis.storage.UnsupportedEncodingException;
+import org.apache.sis.storage.geoheif.internal.Resources;
 import org.apache.sis.storage.isobmff.FullBox;
 import org.apache.sis.storage.isobmff.Reader;
 import org.apache.sis.storage.isobmff.UnsupportedVersionException;
@@ -257,7 +258,8 @@ public final class UncompressedFrameConfig extends FullBox {
             }
             case 1: {
                 if (predefined == null) {
-                    throw new UnsupportedEncodingException("Unsupported 
profile: " + formatFourCC(profile));
+                    throw new UnsupportedEncodingException(Resources.format(
+                            Resources.Keys.UnsupportedImageProfile_1, 
formatFourCC(profile)));
                 }
                 numTileCols    = 1;
                 numTileRows    = 1;

Reply via email to