Repository: FOLIO-FSE/folio_migration_tools (language: python)

Issue:
Add Localization Support to Reports
Discussed at pre-WolfCon 2023 - We should support creating reports in a variety of languages.

This will involve localizing the templates, and any variables used that are not already localized by implementation teams within the FOLIO instance itself.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index ab01cfe4..dd0e6080 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ pipenv install folio-migration-tools
 ### 2. Test the installation by showing the help pages
 ```  
 pipenv run python3 -m folio_migration_tools -h
-```  
+```
 
 # FOLIO migration process
 This repo plays the main part in a process using a collection of tools. The process itself is documented in more detail, including example configuration files, at [this template repository](https://github.com/FOLIO-FSE/migration_repo_template)
@@ -67,7 +67,66 @@ In order to perform migrations according to this process, you need the following
 * Access to the [Data mapping file creator](https://data-mapping-file-creator.folio.ebsco.com/data_mapping_creation) web tool
 * A FOLIO tenant running the latest or the second latest version of FOLIO
 
+# Internationalization
+
+This repo uses [Python-i18n](https://github.com/danhper/python-i18n) to translate reports between languages, and to handle large strings for templates.
+
+**Any English string which will end up in a report** should be wrapped in the function `i18n.t` from `i18n`:
+
+## Keys/Usage
+
+```js
+i18n.t("Reports")+":"
+```
+
+Templating is achieved with `%{[key]}` blocks, and keyword arguments in the internationaliation:
+
+```js
+i18n.t("Code '%{code}' not found in FOLIO",code=folio_code)
+```
+
+Long strings can use a placeholder key:
+
+```js
+i18n.t("blurbs.Introduction.description")
+```
+
+With the full string in ```translations/en.json```:
+
+```json
+"blurbs.Introduction.description": "<br/>Data errors preventing records from being migrated
+```
+
+## Translations Files
+
+Translation files live in the `translations` directory, with `en.json` as the default.
+
+Extract template files with the `extract_translations` script:
+
+```bash
+python scripts/extract_translations.py
+```
+
+## Internationalizations
+
+Other langauges translations live in `translations/[locale].json`.
+For example, Spanish would be `es.json`. 
+
+The keys must match the English keys, but the Values should be translated.
+
+You can update a language file's keys with:
+
+```bash
+python scripts/update_language.py --target-lang [locale]
+```
+
+Translate all new strings, which begin with `TRANSLATE`, then commit.
+
+## Tips
 
+* Internationalize entire phrases or paragraphs, not just the constitutent words. Syntax and grammar vary significantly between languages.
+* Name template variables as generically as possible in the circumstance, and check translations for reusable translations.
+* In a block with sentences separately followed by values, such as a table, you only need to translate the sentences. 
 
 # Running the scripts
 For information on syntax, what files are needed and produced by the toolkit, refer to the documentation and example files in the [template repository](https://github.com/FOLIO-FSE/migration_repo_template). We are building out the docs section in this repository as well:[Documentation](https://folio-migration-tools.readthedocs.io/en/latest/)
diff --git a/i18n_config.py b/i18n_config.py
new file mode 100644
index 00000000..729df02e
--- /dev/null
+++ b/i18n_config.py
@@ -0,0 +1,9 @@
+from pathlib import Path
+
+settings = {
+    "file_format": "json",
+    "skip_locale_root_data": True,
+    "fallback": "en",
+    "filename_format": "{locale}.{format}",
+    "load_path": [Path(__file__).parent / "translations"],
+}
diff --git a/pyproject.toml b/pyproject.toml
index 170ed215..4a2db66a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,6 +44,7 @@ argparse-prompt = "^0.0.5"
 deepdiff = "^6.2.3"
 pyaml = "^21.10.1"
 httpx = "^0.23.3"
+python-i18n = "^0.3.9"
 
 [tool.poetry.group.dev.dependencies]
 pytest = "^7.1.3"
diff --git a/scripts/extract_translations.py b/scripts/extract_translations.py
new file mode 100644
index 00000000..331063e5
--- /dev/null
+++ b/scripts/extract_translations.py
@@ -0,0 +1,64 @@
+# Extract Translations from Source
+import argparse
+import re
+import json
+from pathlib import Path
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--target-dir",
+        help=("Directory to write the translations."),
+        default="translations",
+    )
+    parser.add_argument(
+        "--source-dir",
+        help=("Directory to pull source files."),
+        default="src",
+    )
+    args = parser.parse_args()
+
+    source_files = Path(args.source_dir).rglob("*.py")
+
+    internationalization_re = re.compile(r"i18n.t\(\s*([\"'])(.*?[^\\])\1", re.MULTILINE)
+    found_keys: set[str] = set()
+    for file in source_files:
+        with open(file) as f:
+            src = f.read()
+        found_keys.update(
+            [
+                x.encode("utf-8").decode("unicode_escape")
+                for (_, x) in internationalization_re.findall(src)
+            ]
+        )
+
+    en_filename = Path(args.target_dir) / "en.json"
+    with open(en_filename) as f:
+        translations = json.load(f)
+
+    # Print unused translations
+    for key in translations:
+        if key.startswith("blurbs."):
+            continue
+        if key not in found_keys:
+            print(f"Use of key '{key}' not found. Check if you should delete it.")
+
+    # Load new translations
+    for key in found_keys:
+        if key not in translations:
+            translations[key] = key
+    # Check for missing format
+    missing_format_re = re.compile(r"(?<!%)\{[^\}]*\}")
+    for key in translations:
+        if isinstance(translations[key], str):
+            if missing_format_re.search(translations[key]):
+                print(f"Key '{key}' may not format correctly: format must have %")
+        else:
+            for subkey in translations[key]:
+                if missing_format_re.search(translations[key][subkey]):
+                    print(
+                        f"Key '{key}' plural '{subkey}' may not format correctly: format must have %"
+                    )
+    # Write
+    with open(en_filename, "w") as f:
+        json.dump(translations, f, sort_keys=True, indent=2)
diff --git a/scripts/update_language.py b/scripts/update_language.py
new file mode 100644
index 00000000..54c788ed
--- /dev/null
+++ b/scripts/update_language.py
@@ -0,0 +1,69 @@
+# Update Language Files with new Keys
+
+from argparse_prompt import PromptParser
+import re
+import json
+from pathlib import Path
+
+if __name__ == "__main__":
+    parser = PromptParser()
+    parser.add_argument(
+        "--translations-dir",
+        help=("Directory to read and write the translations."),
+        default="translations",
+        prompt=False,
+    )
+    parser.add_argument(
+        "--source-lang",
+        help=("Directory to pull source files."),
+        default="en",
+        prompt=False,
+    )
+    parser.add_argument(
+        "--target-lang",
+        help=("Target language to convert to."),
+    )
+    args = parser.parse_args()
+
+    source_filename = Path(args.translations_dir) / f"{args.source_lang}.json"
+    target_filename = Path(args.translations_dir) / f"{args.target_lang}.json"
+    with open(source_filename) as f:
+        source_translations = json.load(f)
+    if target_filename.exists():
+        with open(target_filename) as f:
+            target_translations = json.load(f)
+    else:
+        target_translations = {}
+
+    # Print keys in translation not present in source
+    for key in target_translations:
+        if key not in source_translations:
+            print(
+                f"Key '{key}' in target not in source. Check if it was renamed, or if it is still needed."
+            )
+    # Update target translations
+    for key in source_translations:
+        if key not in target_translations:
+            if isinstance(source_translations[key], str):
+                target_translations[key] = "TRANSLATE ME: " + source_translations[key]
+            else:
+                target_translations[key] = {}
+                for subkey in source_translations[key]:
+                    target_translations[key][subkey] = (
+                        "TRANSLATE ME: " + source_translations[key][subkey]
+                    )
+    # Check for missing format
+    missing_format_re = re.compile(r"(?<!%)\{[^\}]*\}")
+    for key in target_translations:
+        if isinstance(target_translations[key], str):
+            if missing_format_re.search(target_translations[key]):
+                print(f"Key '{key}' may not format correctly: format must have %")
+        else:
+            for subkey in target_translations[key]:
+                if missing_format_re.search(target_translations[key][subkey]):
+                    print(
+                        f"Key '{key}' plural '{subkey}' may not format correctly: format must have %"
+                    )
+    # Write
+    with open(target_filename, "w") as f:
+        json.dump(target_translations, f, sort_keys=True, indent=2)
diff --git a/src/folio_migration_tools/__main__.py b/src/folio_migration_tools/__main__.py
index f9ab7fdb..89016fac 100644
--- a/src/folio_migration_tools/__main__.py
+++ b/src/folio_migration_tools/__main__.py
@@ -1,6 +1,7 @@
 import json
 import logging
 import sys
+from pathlib import Path
 
 import httpx
 import humps
@@ -12,6 +13,10 @@
 from folio_migration_tools.migration_tasks import *  # noqa: F403, F401
 from folio_migration_tools.migration_tasks import migration_task_base
 
+import i18n
+
+i18n.load_config(Path(__file__).parents[2] / "i18n_config.py")
+
 
 def parse_args():
     task_classes = iter(inheritors(migration_task_base.MigrationTaskBase))
@@ -31,13 +36,23 @@ def parse_args():
         "--base_folder_path",
         help=("path to the base folder for this library. Built on migration_repo_template"),
     )
+    parser.add_argument(
+        "--report_language",
+        help=(
+            "Language to write the reports. Defaults english for untranslated languages/strings."
+        ),
+        default="en",
+        prompt=False,
+    )
     return parser.parse_args()
 
 
 def main():
     try:
         task_classes = list(inheritors(migration_task_base.MigrationTaskBase))
+
         args = parse_args()
+        i18n.set("locale", args.report_language)
         with open(args.configuration_path) as config_file_path:
             try:
                 config_file_humped = json.load(config_file_path)
diff --git a/src/folio_migration_tools/circulation_helper.py b/src/folio_migration_tools/circulation_helper.py
index d96fbd7e..b2446c09 100644
--- a/src/folio_migration_tools/circulation_helper.py
+++ b/src/folio_migration_tools/circulation_helper.py
@@ -3,6 +3,7 @@
 import logging
 import re
 import time
+import i18n
 from typing import Set
 
 import httpx
@@ -11,7 +12,6 @@
 
 from folio_migration_tools.helper import Helper
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.transaction_migration.legacy_loan import LegacyLoan
 from folio_migration_tools.transaction_migration.legacy_request import LegacyRequest
 from folio_migration_tools.transaction_migration.transaction_result import (
@@ -36,7 +36,9 @@ def __init__(
 
     def get_user_by_barcode(self, user_barcode):
         if user_barcode in self.missing_patron_barcodes:
-            self.migration_report.add_general_statistics("Users already detected as missing")
+            self.migration_report.add_general_statistics(
+                i18n.t("Users already detected as missing")
+            )
             logging.info("User is already detected as missing")
             return {}
         user_path = f"/users?query=barcode=={user_barcode}"
@@ -52,7 +54,9 @@ def get_user_by_barcode(self, user_barcode):
 
     def get_item_by_barcode(self, item_barcode):
         if item_barcode in self.missing_item_barcodes:
-            self.migration_report.add_general_statistics("Items already detected as missing")
+            self.migration_report.add_general_statistics(
+                i18n.t("Items already detected as missing")
+            )
             logging.info("Item is already detected as missing")
             return {}
         item_path = f"/item-storage/items?query=barcode=={item_barcode}"
@@ -135,7 +139,7 @@ def check_out_by_barcode(self, legacy_loan: LegacyLoan) -> TransactionResult:
         url = f"{self.folio_client.okapi_url}{path}"
         try:
             if legacy_loan.patron_barcode in self.missing_patron_barcodes:
-                error_message = "Patron barcode already detected as missing"
+                error_message = i18n.t("Patron barcode already detected as missing")
                 logging.error(
                     f"{error_message} Patron barcode: {legacy_loan.patron_barcode} "
                     f"Item Barcode:{legacy_loan.item_barcode}"
@@ -176,7 +180,7 @@ def check_out_by_barcode(self, legacy_loan: LegacyLoan) -> TransactionResult:
                 elif "find user with matching barcode" in error_message_from_folio:
                     self.missing_patron_barcodes.add(legacy_loan.patron_barcode)
                     error_message = f"No patron with barcode {legacy_loan.patron_barcode} in FOLIO"
-                    stat_message = "Patron barcode not in FOLIO"
+                    stat_message = i18n.t("Patron barcode not in FOLIO")
                     return TransactionResult(
                         False,
                         False,
@@ -197,7 +201,7 @@ def check_out_by_barcode(self, legacy_loan: LegacyLoan) -> TransactionResult:
                     f"Patron barcode: {legacy_loan.patron_barcode} "
                     f"Item Barcode:{legacy_loan.item_barcode}"
                 )
-                self.migration_report.add(Blurbs.Details, stat_message)
+                self.migration_report.add("Details", stat_message)
                 return TransactionResult(
                     False, True, None, error_message, f"Check out error: {stat_message}"
                 )
@@ -234,7 +238,7 @@ def check_out_by_barcode(self, legacy_loan: LegacyLoan) -> TransactionResult:
                 False,
                 None,
                 "5XX",
-                f"Failed checkout http status {req.status_code}",
+                i18n.t("Failed checkout http status %{code}", code=req.status_code),
             )
 
     @staticmethod
@@ -272,7 +276,7 @@ def create_request(
                 return True
         except Exception as exception:
             logging.error(exception, exc_info=True)
-            migration_report.add(Blurbs.Details, exception)
+            migration_report.add("Details", exception)
             Helper.log_data_issue(
                 legacy_request.item_barcode,
                 exception,
diff --git a/src/folio_migration_tools/custom_exceptions.py b/src/folio_migration_tools/custom_exceptions.py
index 9d2e251b..97f94b17 100644
--- a/src/folio_migration_tools/custom_exceptions.py
+++ b/src/folio_migration_tools/custom_exceptions.py
@@ -1,4 +1,5 @@
 import logging
+import i18n
 
 
 class TransfomationError(Exception):
@@ -17,8 +18,8 @@ def __init__(self, index_or_id="", message="", data_value=""):
 
     def __str__(self):
         return (
-            f"Data issue. Consider fixing the record. "
-            f"\t{self.index_or_id}\t{self.message}\t{self.data_value}"
+            i18n.t("Data issue. Consider fixing the record. ")
+            + f"\t{self.index_or_id}\t{self.message}\t{self.data_value}"
         )
 
     def log_it(self):
diff --git a/src/folio_migration_tools/helper.py b/src/folio_migration_tools/helper.py
index e6c03f26..652aae27 100644
--- a/src/folio_migration_tools/helper.py
+++ b/src/folio_migration_tools/helper.py
@@ -1,5 +1,6 @@
 import json
 import logging
+import i18n
 
 
 class Helper:
@@ -7,16 +8,18 @@ class Helper:
     def print_mapping_report(
         report_file, total_records: int, mapped_folio_fields, mapped_legacy_fields
     ):
-        details_start = "<details><summary>Click to expand field report</summary>     \n\n"
-        details_end = "</details>   \n"
-        report_file.write("\n## Mapped FOLIO fields\n")
+        details_start = (
+            "<details><summary>" + i18n.t("Click to expand field report") + "</summary>\n\n"
+        )
+        details_end = "</details>\n"
+        report_file.write("\n## " + i18n.t("Mapped FOLIO fields") + "\n")
         # report_file.write(f"{blurbs[header]}\n")
 
         d_sorted = {k: mapped_folio_fields[k] for k in sorted(mapped_folio_fields)}
         report_file.write(details_start)
-
-        report_file.write("FOLIO Field | Mapped | Unmapped  \n")
-        report_file.write("--- | --- | ---:  \n")
+        columns = [i18n.t("FOLIO Field"), i18n.t("Mapped"), i18n.t("Unmapped")]
+        report_file.write(" | ".join(columns) + "\n")
+        report_file.write("|".join(len(columns) * ["---"]) + "\n")
         for k, v in d_sorted.items():
             unmapped = max(total_records - v[0], 0)
             mapped = v[0]
@@ -29,13 +32,14 @@ def print_mapping_report(
             )
         report_file.write(details_end)
 
-        report_file.write("\n## Mapped Legacy fields\n")
+        report_file.write("\n## " + i18n.t("Mapped Legacy fields") + "\n")
         # report_file.write(f"{blurbs[header]}\n")
 
         d_sorted = {k: mapped_legacy_fields[k] for k in sorted(mapped_legacy_fields)}
         report_file.write(details_start)
-        report_file.write("Legacy Field | Present | Mapped | Unmapped  \n")
-        report_file.write("--- | --- | --- | ---:  \n")
+        columns = [i18n.t("Legacy Field"), i18n.t("Present"), i18n.t("Mapped"), i18n.t("Unmapped")]
+        report_file.write("|".join(columns) + "\n")
+        report_file.write("|".join(len(columns) * ["---"]) + "\n")
         for k, v in d_sorted.items():
             present = v[0]
             present_per = "{:.1%}".format(present / total_records if total_records else 0)
diff --git a/src/folio_migration_tools/holdings_helper.py b/src/folio_migration_tools/holdings_helper.py
index ddecb967..f2e09b67 100644
--- a/src/folio_migration_tools/holdings_helper.py
+++ b/src/folio_migration_tools/holdings_helper.py
@@ -1,11 +1,11 @@
 import json
 import logging
+import i18n
 from uuid import uuid4
 
 from folio_migration_tools import custom_exceptions
 from folio_migration_tools import helper
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class HoldingsHelper:
@@ -43,15 +43,18 @@ def to_key(
                 v = holdings_record.get(fields_criteria, "")
                 if not v:
                     migration_report.add(
-                        Blurbs.HoldingsMerging, f"{fields_criteria} empty or not set"
+                        "HoldingsMerging",
+                        i18n.t(
+                            "%{fields_criteria} empty or not set", fields_criteria=fields_criteria
+                        ),
                     )
                 values.append(v)
 
             if holdings_record.get("holdingsTypeId") == holdings_type_id_to_exclude_from_merging:
                 values.append(str(uuid4()))
                 migration_report.add(
-                    Blurbs.HoldingsMerging,
-                    "Holding prevented from merging by holdingsTypeId",
+                    "HoldingsMerging",
+                    i18n.t("Holding prevented from merging by holdingsTypeId"),
                 )
             return "-".join(values)
         except Exception as exception:
@@ -95,13 +98,13 @@ def load_previously_generated_holdings(
                         prev_holdings[stored_key], stored_holding
                     )
                     migration_report.add(
-                        Blurbs.HoldingsMerging,
-                        "Duplicate key based on current merge criteria. Records merged",
+                        "HoldingsMerging",
+                        i18n.t("Duplicate key based on current merge criteria. Records merged"),
                     )
                 else:
                     migration_report.add(
-                        Blurbs.HoldingsMerging,
-                        "Previously transformed holdings record loaded",
+                        "HoldingsMerging",
+                        i18n.t("Previously transformed holdings record loaded"),
                     )
                     prev_holdings[stored_key] = stored_holding
             return prev_holdings
diff --git a/src/folio_migration_tools/mapper_base.py b/src/folio_migration_tools/mapper_base.py
index 3808be39..3d958fdb 100644
--- a/src/folio_migration_tools/mapper_base.py
+++ b/src/folio_migration_tools/mapper_base.py
@@ -4,6 +4,7 @@
 import logging
 import sys
 import uuid
+import i18n
 from datetime import datetime
 from datetime import timezone
 from pathlib import Path
@@ -21,7 +22,6 @@
     RefDataMapping,
 )
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class MapperBase:
@@ -111,7 +111,7 @@ def get_mapped_name(
             if not right_mapping:
                 raise StopIteration()
             self.migration_report.add(
-                ref_data_mapping.blurb,
+                ref_data_mapping.blurb_id,
                 (
                     f'{" - ".join(fieldvalues)} '
                     f'-> {right_mapping[f"folio_{ref_data_mapping.key_type}"]}'
@@ -122,12 +122,12 @@ def get_mapped_name(
         except StopIteration:
             if prevent_default:
                 self.migration_report.add(
-                    ref_data_mapping.blurb,
+                    ref_data_mapping.blurb_id,
                     (f"Not to be mapped. " f'(No default) -- {" - ".join(fieldvalues)} -> ""'),
                 )
                 return ""
             self.migration_report.add(
-                ref_data_mapping.blurb,
+                ref_data_mapping.blurb_id,
                 (
                     f"Unmapped (Default value was set) -- "
                     f'{" - ".join(fieldvalues)} -> {ref_data_mapping.default_name}'
@@ -184,7 +184,7 @@ def get_mapped_ref_data_value(
             if not right_mapping:
                 raise StopIteration()
             self.migration_report.add(
-                ref_data_mapping.blurb,
+                ref_data_mapping.blurb_id,
                 (
                     f'{" - ".join(fieldvalues)} '
                     f'-> {right_mapping[f"folio_{ref_data_mapping.key_type}"]}'
@@ -194,12 +194,12 @@ def get_mapped_ref_data_value(
         except StopIteration:
             if prevent_default:
                 self.migration_report.add(
-                    ref_data_mapping.blurb,
+                    ref_data_mapping.blurb_id,
                     (f"Not to be mapped. " f'(No default) -- {" - ".join(fieldvalues)} -> ""'),
                 )
                 return ""
             self.migration_report.add(
-                ref_data_mapping.blurb,
+                ref_data_mapping.blurb_id,
                 (
                     f"Unmapped (Default value was set) -- "
                     f'{" - ".join(fieldvalues)} -> {ref_data_mapping.default_name}'
@@ -225,13 +225,13 @@ def get_mapped_ref_data_value(
             ) from exception
 
     def handle_transformation_field_mapping_error(self, index_or_id, error):
-        self.migration_report.add(Blurbs.FieldMappingErrors, error)
+        self.migration_report.add("FieldMappingErrors", error)
         error.id = error.id or index_or_id
         error.log_it()
-        self.migration_report.add_general_statistics("Field Mapping Errors found")
+        self.migration_report.add_general_statistics(i18n.t("Field Mapping Errors found"))
 
     def handle_transformation_process_error(self, idx, error: TransformationProcessError):
-        self.migration_report.add_general_statistics("Transformation process error")
+        self.migration_report.add_general_statistics(i18n.t("Transformation process error"))
         logging.critical("%s\t%s", idx, error)
         print(f"\n{error.message}: {error.data_value}")
         sys.exit(1)
@@ -240,7 +240,7 @@ def handle_transformation_record_failed_error(
         self, records_processed: int, error: TransformationRecordFailedError
     ):
         self.migration_report.add(
-            Blurbs.GeneralStatistics, "FAILED Records failed due to an error"
+            "GeneralStatistics", i18n.t("FAILED Records failed due to an error")
         )
         error.index_or_id = error.index_or_id or records_processed
         error.log_it()
@@ -318,7 +318,7 @@ def save_id_map_file(self, path, legacy_map: dict):
             for id_string in legacy_map.values():
                 legacy_map_file.write(f"{json.dumps(id_string)}\n")
                 self.migration_report.add(
-                    Blurbs.GeneralStatistics, "Unique ID:s written to legacy map"
+                    "GeneralStatistics", i18n.t("Unique ID:s written to legacy map")
                 )
         logging.info("Wrote legacy id map to %s", path)
 
@@ -372,7 +372,9 @@ def clean_none_props(d: dict):
 
     def add_legacy_id_to_admin_note(self, folio_record: dict, legacy_id: str):
         if not legacy_id:
-            raise TransformationFieldMappingError(legacy_id, "Legacy id is empty", legacy_id)
+            raise TransformationFieldMappingError(
+                legacy_id, i18n.t("Legacy id is empty"), legacy_id
+            )
         if "administrativeNotes" not in folio_record:
             folio_record["administrativeNotes"] = []
         if id_string := next(
@@ -439,7 +441,7 @@ def create_bound_with_holdings(
                 )
                 if bound_with_holding.get("hrid", ""):
                     bound_with_holding["hrid"] = f'{bound_with_holding["hrid"]}_bw_{bwidx}'
-            self.migration_report.add_general_statistics("Bound-with holdings created")
+            self.migration_report.add_general_statistics(i18n.t("Bound-with holdings created"))
             yield bound_with_holding
 
     def generate_boundwith_holding_uuid(self, holding_uuid, instance_uuid):
diff --git a/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py b/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py
index 851202e5..a84eb494 100644
--- a/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py
@@ -1,3 +1,4 @@
+import i18n
 from typing import Any
 from typing import Dict
 
@@ -14,7 +15,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class CoursesMapper(MappingFileMapperBase):
@@ -54,7 +54,7 @@ def __init__(
                 "terms",
                 terms_map,
                 "name",
-                Blurbs.TermsMapping,
+                "TermsMapping",
             )
         else:
             self.terms_map = None
@@ -66,7 +66,7 @@ def __init__(
                 "departments",
                 departments_map,
                 "name",
-                Blurbs.DepartmentsMapping,
+                "DepartmentsMapping",
             )
         else:
             self.departments_map = None
@@ -74,13 +74,13 @@ def __init__(
     def store_objects(self, composite_course):
         try:
             self.extradata_writer.write("courselisting", composite_course[0]["courselisting"])
-            self.migration_report.add_general_statistics("Stored courselistings")
+            self.migration_report.add_general_statistics(i18n.t("Stored courselistings"))
             self.extradata_writer.write("course", composite_course[0]["course"])
-            self.migration_report.add_general_statistics("Stored courses")
+            self.migration_report.add_general_statistics(i18n.t("Stored courses"))
             if "instructors" in composite_course[0] and any(composite_course[0]["instructors"]):
                 for instructor in composite_course[0]["instructors"]:
                     self.extradata_writer.write("instructor", instructor)
-                    self.migration_report.add_general_statistics("Stored instructors")
+                    self.migration_report.add_general_statistics(i18n.t("Stored instructors"))
 
         except Exception as ee:
             raise TransformationRecordFailedError(
@@ -106,7 +106,7 @@ def perform_additional_mappings(self, composite_course):
                     if self.task_configuration.look_up_instructor:
                         self.populate_instructor_from_users(instructor)
             else:
-                self.migration_report.add_general_statistics("Missing Instructors")
+                self.migration_report.add_general_statistics(i18n.t("Missing Instructors"))
 
             # Link course to courselisting
             composite_course[0]["course"]["courseListingId"] = composite_course[0][
@@ -162,7 +162,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
         ):
             return mapped_value
         else:
-            self.migration_report.add(Blurbs.UnmappedProperties, f"{folio_prop_name}")
+            self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
             return ""
 
     def get_composite_course_schema(self) -> Dict[str, Any]:
diff --git a/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py b/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py
index 1c60b9d6..b0a6c789 100644
--- a/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py
@@ -1,4 +1,5 @@
 import ast
+import i18n
 
 from folio_uuid.folio_uuid import FOLIONamespaces
 from folioclient import FolioClient
@@ -12,7 +13,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class HoldingsMapper(MappingFileMapperBase):
@@ -44,7 +44,7 @@ def __init__(
             "locations",
             location_map,
             "code",
-            Blurbs.LocationMapping,
+            "LocationMapping",
         )
         if call_number_type_map:
             self.call_number_mapping = RefDataMapping(
@@ -53,7 +53,7 @@ def __init__(
                 "callNumberTypes",
                 call_number_type_map,
                 "name",
-                Blurbs.CallNumberTypeMapping,
+                "CallNumberTypeMapping",
             )
 
     def perform_additional_mappings(self, folio_rec, file_def):
@@ -62,8 +62,8 @@ def perform_additional_mappings(self, folio_rec, file_def):
     def handle_suppression(self, folio_record, file_def: FileDefinition):
         folio_record["discoverySuppress"] = file_def.discovery_suppressed
         self.migration_report.add(
-            Blurbs.Suppression,
-            f'Suppressed from discovery = {folio_record["discoverySuppress"]}',
+            "Suppression",
+            i18n.t("Suppressed from discovery") + f' = {folio_record["discoverySuppress"]}',
         )
 
     def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_value):
@@ -84,14 +84,16 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
         elif mapped_value:
             return mapped_value
         else:
-            self.migration_report.add(Blurbs.UnmappedProperties, f"{folio_prop_name}")
+            self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
             return ""
 
     def get_call_number(self, legacy_value):
         if legacy_value.startswith("[") and len(legacy_value.split(",")) > 1:
-            self.migration_report.add_general_statistics("Bound-with items callnumber identified")
+            self.migration_report.add_general_statistics(
+                i18n.t("Bound-with items callnumber identified")
+            )
             self.migration_report.add(
-                Blurbs.BoundWithMappings,
+                "BoundWithMappings",
                 (f"Number of bib-level callnumbers in record: {len(legacy_value.split(','))}"),
             )
         return legacy_value
@@ -112,7 +114,7 @@ def get_call_number_type_id(self, legacy_item, folio_prop_name: str, id_or_index
             return self.get_mapped_ref_data_value(
                 self.call_number_mapping, legacy_item, id_or_index, folio_prop_name
             )
-        self.migration_report.add(Blurbs.CallNumberTypeMapping, "No Call Number Type Mapping")
+        self.migration_report.add("CallNumberTypeMapping", i18n.t("No Call Number Type Mapping"))
         return ""
 
     def get_instance_ids(self, legacy_value: str, index_or_id: str):
@@ -120,8 +122,8 @@ def get_instance_ids(self, legacy_value: str, index_or_id: str):
         return_ids = []
         legacy_bib_ids = self.get_legacy_bib_ids(legacy_value, index_or_id)
         self.migration_report.add(
-            Blurbs.BoundWithMappings,
-            f"Number of bib records referenced in item: {len(legacy_bib_ids)}",
+            "BoundWithMappings",
+            i18n.t("Number of bib records referenced in item") + f": {len(legacy_bib_ids)}",
         )
         for legacy_instance_id in legacy_bib_ids:
             new_legacy_value = (
@@ -133,11 +135,15 @@ def get_instance_ids(self, legacy_value: str, index_or_id: str):
                 new_legacy_value not in self.instance_id_map
                 and legacy_instance_id not in self.instance_id_map
             ):
-                self.migration_report.add_general_statistics("Records not matched to Instances")
+                self.migration_report.add_general_statistics(
+                    i18n.t("Records not matched to Instances")
+                )
                 s = "Bib id not in instance id map."
                 raise TransformationRecordFailedError(index_or_id, s, new_legacy_value)
             else:
-                self.migration_report.add_general_statistics("Records matched to Instances")
+                self.migration_report.add_general_statistics(
+                    i18n.t("Records matched to Instances")
+                )
                 entry = self.instance_id_map.get(new_legacy_value, "") or self.instance_id_map.get(
                     legacy_instance_id
                 )
@@ -157,11 +163,11 @@ def get_legacy_bib_ids(self, legacy_value: str, index_or_id: str) -> list[str]:
             new_value_len = len(new_legacy_values)
             if new_value_len > 1:
                 self.migration_report.add_general_statistics(
-                    "Bound-with items identified by bib id"
+                    i18n.t("Bound-with items identified by bib id")
                 )
                 self.migration_report.add(
-                    Blurbs.GeneralStatistics,
-                    "Bib ids referenced in bound-with items",
+                    "GeneralStatistics",
+                    i18n.t("Bib ids referenced in bound-with items"),
                     new_value_len,
                 )
             return new_legacy_values
diff --git a/src/folio_migration_tools/mapping_file_transformation/item_mapper.py b/src/folio_migration_tools/mapping_file_transformation/item_mapper.py
index 05aedc39..3ad35cdf 100644
--- a/src/folio_migration_tools/mapping_file_transformation/item_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/item_mapper.py
@@ -5,6 +5,7 @@
 from datetime import timezone
 from typing import Set
 from uuid import uuid4
+import i18n
 
 from folio_uuid.folio_uuid import FOLIONamespaces
 from folioclient import FolioClient
@@ -20,7 +21,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class ItemMapper(MappingFileMapperBase):
@@ -61,7 +61,7 @@ def __init__(
                 "loantypes",
                 temporary_loan_type_mapping,
                 "name",
-                Blurbs.TemporaryLoanTypeMapping,
+                "TemporaryLoanTypeMapping",
             )
         self.temp_location_mapping = None
         if temporary_location_mapping:
@@ -71,7 +71,7 @@ def __init__(
                 "locations",
                 temporary_location_mapping,
                 "code",
-                Blurbs.TemporaryLocationMapping,
+                "TemporaryLocationMapping",
             )
 
         if item_statuses_map:
@@ -83,7 +83,7 @@ def __init__(
                 "callNumberTypes",
                 call_number_type_map,
                 "name",
-                Blurbs.CallNumberTypeMapping,
+                "CallNumberTypeMapping",
             )
         self.loan_type_mapping = RefDataMapping(
             self.folio_client,
@@ -91,7 +91,7 @@ def __init__(
             "loantypes",
             loan_type_map,
             "name",
-            Blurbs.PermanentLoanTypeMapping,
+            "PermanentLoanTypeMapping",
         )
         self.boundwith_relationship_map = self.setup_boundwith_relationship_map(
             boundwith_relationship_map
@@ -103,7 +103,7 @@ def __init__(
             "mtypes",
             material_type_map,
             "name",
-            Blurbs.MaterialTypeMapping,
+            "MaterialTypeMapping",
         )
 
         self.location_mapping = RefDataMapping(
@@ -112,7 +112,7 @@ def __init__(
             "locations",
             location_map,
             "code",
-            Blurbs.LocationMapping,
+            "LocationMapping",
         )
 
     def perform_additional_mappings(self, folio_rec, file_def):
@@ -121,8 +121,8 @@ def perform_additional_mappings(self, folio_rec, file_def):
     def handle_suppression(self, folio_record, file_def: FileDefinition):
         folio_record["discoverySuppress"] = file_def.discovery_suppressed
         self.migration_report.add(
-            Blurbs.Suppression,
-            f'Suppressed from discovery = {folio_record["discoverySuppress"]}',
+            "Suppression",
+            i18n.t("Suppressed from discovery") + f' = {folio_record["discoverySuppress"]}',
         )
 
     def setup_status_mapping(self, item_statuses_map):
@@ -177,7 +177,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
                 index_or_id,
                 True,
             )
-            self.migration_report.add(Blurbs.TemporaryLocationMapping, f"{temp_loc}")
+            self.migration_report.add("TemporaryLocationMapping", f"{temp_loc}")
             return temp_loc
         elif folio_prop_name == "materialTypeId":
             return self.get_mapped_ref_data_value(
@@ -200,9 +200,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
                 index_or_id,
                 True,
             )
-            self.migration_report.add(
-                Blurbs.TemporaryLoanTypeMapping, f"{folio_prop_name} -> {ltid}"
-            )
+            self.migration_report.add("TemporaryLoanTypeMapping", f"{folio_prop_name} -> {ltid}")
             return ltid
         elif folio_prop_name == "permanentLoanTypeId":
             return self.get_mapped_ref_data_value(
@@ -213,7 +211,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
                 legacy_item, folio_prop_name, index_or_id
             )
             self.migration_report.add(
-                Blurbs.StatisticalCodeMapping,
+                "StatisticalCodeMapping",
                 f"{folio_prop_name} -> {statistical_code_id}",
             )
             return statistical_code_id
@@ -227,7 +225,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
             barcode = mapped_value
             if barcode.strip() and barcode in self.unique_barcodes:
                 Helper.log_data_issue(index_or_id, "Duplicate barcode", mapped_value)
-                self.migration_report.add_general_statistics("Duplicate barcodes")
+                self.migration_report.add_general_statistics(i18n.t("Duplicate barcodes"))
                 return f"{barcode}-{uuid4()}"
             else:
                 if barcode.strip():
@@ -239,7 +237,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
             elif f"{self.bib_id_template}{mapped_value}" in self.holdings_id_map:
                 return self.holdings_id_map[f"{self.bib_id_template}{mapped_value}"][1]
             self.migration_report.add_general_statistics(
-                "Records failed because of failed holdings",
+                i18n.t("Records failed because of failed holdings"),
             )
             s = (
                 "Holdings id referenced in legacy item "
@@ -249,7 +247,7 @@ def get_prop(self, legacy_item, folio_prop_name, index_or_id, schema_default_val
         elif mapped_value:
             return mapped_value
         else:
-            self.migration_report.add(Blurbs.UnmappedProperties, f"{folio_prop_name}")
+            self.migration_report.add("UnmappedProperties", f"{folio_prop_name}")
             return ""
 
     def get_item_level_call_number_type_id(self, legacy_item, folio_prop_name: str, index_or_id):
@@ -258,12 +256,12 @@ def get_item_level_call_number_type_id(self, legacy_item, folio_prop_name: str,
                 self.call_number_mapping, legacy_item, index_or_id, folio_prop_name
             )
         self.migration_report.add(
-            Blurbs.CallNumberTypeMapping,
-            "Mapping not setup",
+            "CallNumberTypeMapping",
+            i18n.t("Mapping not setup"),
         )
         return ""
 
     def transform_status(self, legacy_value):
         status = self.status_mapping.get(legacy_value, "Available")
-        self.migration_report.add(Blurbs.StatusMapping, f"'{legacy_value}' -> {status}")
+        self.migration_report.add("StatusMapping", f"'{legacy_value}' -> {status}")
         return status
diff --git a/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py b/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py
index 9ab7a129..89ac1ba7 100644
--- a/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py
@@ -1,6 +1,7 @@
 import json
 import logging
 import uuid
+import i18n
 from typing import Any
 from typing import Dict
 from zoneinfo import ZoneInfo
@@ -19,7 +20,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class ManualFeeFinesMapper(MappingFileMapperBase):
@@ -60,7 +60,7 @@ def __init__(
                 "owners",
                 feefines_owner_map,
                 "owner",
-                Blurbs.FeeFineOnwerMapping,
+                "FeeFineOnwerMapping",
             )
         else:
             self.feefines_owner_map = None
@@ -72,7 +72,7 @@ def __init__(
                 "feefines",
                 feefines_type_map,
                 "feeFineType",
-                Blurbs.FeeFineTypesMapping,
+                "FeeFineTypesMapping",
             )
         else:
             self.feefines_type_map = None
@@ -84,7 +84,7 @@ def __init__(
                 "servicepoints",
                 service_point_map,
                 "name",
-                Blurbs.FeeFineServicePointTypesMapping,
+                "FeeFineServicePointTypesMapping",
             )
         else:
             self.service_point_map = None
@@ -92,9 +92,9 @@ def __init__(
     def store_objects(self, composite_feefine):
         try:
             self.extradata_writer.write("account", composite_feefine["account"])
-            self.migration_report.add_general_statistics("TOTAL Accounts created")
+            self.migration_report.add_general_statistics(i18n.t("TOTAL Accounts created"))
             self.extradata_writer.write("feefineaction", composite_feefine["feefineaction"])
-            self.migration_report.add_general_statistics("TOTAL Feefineactions created")
+            self.migration_report.add_general_statistics(i18n.t("TOTAL Feefineactions created"))
 
         except Exception as ee:
             raise TransformationRecordFailedError(
@@ -189,8 +189,8 @@ def parse_date_with_tenant_timezone(self, folio_prop_name: str, index_or_id, map
             return format_date.isoformat()
         except Exception:
             self.migration_report.add(
-                Blurbs.GeneralStatistics,
-                "DATA ISSUE Invalid dates",
+                "GeneralStatistics",
+                i18n.t("DATA ISSUE Invalid dates"),
             )
             logging.log(
                 26,
@@ -205,8 +205,8 @@ def parse_sum_as_float(self, index_or_id, legacy_sum, folio_prop_name):
             return float(legacy_sum)
         except Exception as ee:
             self.migration_report.add(
-                Blurbs.GeneralStatistics,
-                "DATA ISSUE Invalid sum",
+                "GeneralStatistics",
+                i18n.t("DATA ISSUE Invalid sum"),
             )
             raise TransformationRecordFailedError(
                 index_or_id,
@@ -240,8 +240,8 @@ def get_folio_user_uuid(self, index_or_id, user_barcode):
             return matching_user["id"]
         else:
             self.migration_report.add(
-                Blurbs.GeneralStatistics,
-                "DATA ISSUE Users not in FOLIO",
+                "GeneralStatistics",
+                i18n.t("DATA ISSUE Users not in FOLIO"),
             )
             raise TransformationRecordFailedError(
                 index_or_id,
@@ -297,8 +297,8 @@ def enrich_with_folio_item_data(self, index_or_id, feefine, item_barcode):
         else:
             feefine["account"].pop("itemId")
             self.migration_report.add(
-                Blurbs.GeneralStatistics,
-                "DATA ISSUE Items not in FOLIO",
+                "GeneralStatistics",
+                i18n.t("DATA ISSUE Items not in FOLIO"),
             )
             logging.log(
                 26,
diff --git a/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py b/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py
index 94e3aaef..51b458fd 100644
--- a/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py
+++ b/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py
@@ -4,6 +4,7 @@
 import logging
 import re
 import uuid
+import i18n
 from functools import reduce
 from pathlib import Path
 from typing import Dict
@@ -24,7 +25,6 @@
     RefDataMapping,
 )
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 empty_vals = ["Not mapped", None, ""]
 
@@ -110,7 +110,7 @@ def setup_statistical_codes_map(self, statistical_codes_map):
                 "statisticalCodes",
                 statistical_codes_map,
                 "code",
-                Blurbs.StatisticalCodeMapping,
+                "StatisticalCodeMapping",
             )
             logging.info("Statistical codes mapping set up")
         else:
@@ -241,8 +241,8 @@ def get_statistical_code(self, legacy_item: dict, folio_prop_name: str, index_or
                 True,
             )
         self.migration_report.add(
-            Blurbs.StatisticalCodeMapping,
-            "Mapping not setup",
+            "StatisticalCodeMapping",
+            i18n.t("Mapping not setup"),
         )
         return ""
 
@@ -256,7 +256,9 @@ def get_prop(self, legacy_object, folio_prop_name, index_or_id, schema_default_v
         if not any(map_entries):
             return ""
         elif len(map_entries) > 1:
-            self.migration_report.add(Blurbs.Details, f"{legacy_item_keys} were concatenated")
+            self.migration_report.add(
+                "Details", i18n.t("%{props} were concatenated", props=legacy_item_keys)
+            )
             return " ".join(
                 MappingFileMapperBase.get_legacy_value(
                     legacy_object,
@@ -279,8 +281,12 @@ def get_prop(self, legacy_object, folio_prop_name, index_or_id, schema_default_v
                 return legacy_value
             else:
                 self.migration_report.add(
-                    Blurbs.FolioDefaultValuesAdded,
-                    f"{schema_default_value} added to {folio_prop_name}",
+                    "FolioDefaultValuesAdded",
+                    i18n.t(
+                        "%{schema_value} added to %{prop_name}",
+                        schema_value=schema_default_value,
+                        prop_name=folio_prop_name,
+                    ),
                 )
                 return schema_default_value
 
@@ -370,8 +376,12 @@ def get_legacy_value(
         ):
             value_mapped_value = mapping_file_entry.get("value")
             migration_report.add(
-                Blurbs.DefaultValuesAdded,
-                f"{value_mapped_value} added to {mapping_file_entry.get('folio_field', '')}",
+                "DefaultValuesAdded",
+                i18n.t(
+                    "%{value} added to %{entry}",
+                    value=value_mapped_value,
+                    entry=mapping_file_entry.get("folio_field", ""),
+                ),
             )
             return value_mapped_value
 
@@ -390,7 +400,7 @@ def get_legacy_value(
 
             if replaced_val or isinstance(replaced_val, bool):
                 migration_report.add(
-                    Blurbs.FieldMappingDetails,
+                    "FieldMappingDetails",
                     (
                         f"Replaced {value} in {mapping_file_entry['legacy_field']} "
                         f"with {replaced_val}"
@@ -404,7 +414,7 @@ def get_legacy_value(
             value = re.findall(my_pattern, value)[0]
         if not value and mapping_file_entry.get("fallback_legacy_field", ""):
             migration_report.add(
-                Blurbs.FieldMappingDetails,
+                "FieldMappingDetails",
                 (
                     f"Added fallback value from {mapping_file_entry['fallback_legacy_field']} "
                     f"instead of {mapping_file_entry['legacy_field']}"
@@ -415,7 +425,7 @@ def get_legacy_value(
             ).strip()
         if not value and mapping_file_entry.get("fallback_value", ""):
             migration_report.add(
-                Blurbs.FieldMappingDetails,
+                "FieldMappingDetails",
                 (
                     f"Added fallback value {mapping_file_entry['fallback_value']} "
                     f"instead of empty {mapping_file_entry['legacy_field']}"
@@ -708,10 +718,10 @@ def get_objects(self, source_file, file_name: Path):
         logging.info("Source data file contains %d rows", total_rows)
         logging.info("Source data file contains %d empty rows", empty_rows)
         self.migration_report.set(
-            Blurbs.GeneralStatistics, "Number of rows in {}".format(file_name.name), total_rows
+            "GeneralStatistics", "Number of rows in {}".format(file_name.name), total_rows
         )
         self.migration_report.set(
-            Blurbs.GeneralStatistics,
+            "GeneralStatistics",
             "Number of empty rows in {}".format(file_name.name),
             empty_rows,
         )
@@ -852,7 +862,7 @@ def validate_object_items_in_array(self, folio_object, schema_property_name, sch
                 valid_array_objects.append(item)
             else:
                 self.migration_report.add(
-                    Blurbs.IncompleteSubPropertyRemoved,
+                    "IncompleteSubPropertyRemoved",
                     f"{schema_property_name}",
                 )
         if valid_array_objects:
diff --git a/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py b/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py
index 8f14a30e..ee107f63 100644
--- a/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py
@@ -1,4 +1,5 @@
 import logging
+import i18n
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
 from folioclient import FolioClient
@@ -7,7 +8,6 @@
 from folio_migration_tools.mapping_file_transformation.mapping_file_mapper_base import (
     MappingFileMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class NotesMapper(MappingFileMapperBase):
@@ -59,11 +59,13 @@ def map_notes(self, legacy_object, legacy_id, object_uuid: str, record_type: FOL
                     if "type" in note:
                         del note["type"]
                     self.extradata_writer.write("notes", note)
-                    self.migration_report.add_general_statistics("Number of linked notes created")
-                    self.migration_report.add(Blurbs.MappedNoteTypes, note["typeId"])
+                    self.migration_report.add_general_statistics(
+                        i18n.t("Number of linked notes created")
+                    )
+                    self.migration_report.add("MappedNoteTypes", note["typeId"])
                 else:
                     self.migration_report.add_general_statistics(
-                        "Number of discarded notes with no content"
+                        i18n.t("Number of discarded notes with no content")
                     )
 
     def get_notes_schema(self):
diff --git a/src/folio_migration_tools/mapping_file_transformation/order_mapper.py b/src/folio_migration_tools/mapping_file_transformation/order_mapper.py
index 68cd1a0c..a4abb4f7 100644
--- a/src/folio_migration_tools/mapping_file_transformation/order_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/order_mapper.py
@@ -5,6 +5,7 @@
 import sys
 import urllib.parse
 import uuid
+import i18n
 
 import httpx
 from folio_uuid.folio_uuid import FOLIONamespaces
@@ -21,7 +22,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class CompositeOrderMapper(MappingFileMapperBase):
@@ -64,7 +64,7 @@ def __init__(
             "acquisitionMethods",
             acquisition_method_map,
             "value",
-            Blurbs.AcquisitionMethodMapping,
+            "AcquisitionMethodMapping",
         )
         logging.info("Init done")
         self.location_mapping = RefDataMapping(
@@ -73,7 +73,7 @@ def __init__(
             "locations",
             location_map,
             "code",
-            Blurbs.OrderLineLocationMapping,
+            "OrderLineLocationMapping",
         )
 
         self.folio_client: FolioClient = folio_client
@@ -398,8 +398,8 @@ def get_matching_record_from_folio(
     def get_folio_organization_uuid(self, index_or_id, org_code):
         if self.organizations_id_map:
             self.migration_report.add(
-                Blurbs.PurchaseOrderVendorLinking,
-                "Organizations linked using organizations_id_map",
+                "PurchaseOrderVendorLinking",
+                i18n.t("Organizations linked using organizations_id_map"),
             )
             if matching_org := self.organizations_id_map.get(org_code):
                 return matching_org[1]
@@ -413,15 +413,15 @@ def get_folio_organization_uuid(self, index_or_id, org_code):
             "organizations",
         ):
             self.migration_report.add(
-                Blurbs.PurchaseOrderVendorLinking,
-                "Organizations not in ID map, linked using FOLIO lookup",
+                "PurchaseOrderVendorLinking",
+                i18n.t("Organizations not in ID map, linked using FOLIO lookup"),
             )
             return matching_org["id"]
 
         else:
             self.migration_report.add(
-                Blurbs.PurchaseOrderVendorLinking,
-                "RECORD FAILED Organization identifier not in ID map/FOLIO",
+                "PurchaseOrderVendorLinking",
+                i18n.t("RECORD FAILED Organization identifier not in ID map/FOLIO"),
             )
             raise TransformationRecordFailedError(
                 index_or_id,
@@ -432,14 +432,14 @@ def get_folio_organization_uuid(self, index_or_id, org_code):
     def get_folio_instance_uuid(self, index_or_id, bib_id):
         if matching_instance := self.instance_id_map.get(bib_id):
             self.migration_report.add(
-                Blurbs.PurchaseOrderInstanceLinking,
-                "Instances linked using instances_id_map",
+                "PurchaseOrderInstanceLinking",
+                i18n.t("Instances linked using instances_id_map"),
             )
             return matching_instance[1]
         else:
             self.migration_report.add(
-                Blurbs.PurchaseOrderInstanceLinking,
-                "Bib identifier not in instances_id_map, no instance linked",
+                "PurchaseOrderInstanceLinking",
+                i18n.t("Bib identifier not in instances_id_map, no instance linked"),
             )
             Helper.log_data_issue(
                 index_or_id,
diff --git a/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py b/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py
index 4ebb757d..ede8541a 100644
--- a/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py
@@ -16,7 +16,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class OrganizationMapper(MappingFileMapperBase):
@@ -134,21 +133,21 @@ def set_up_reference_data_mapping(
 
         if address_categories_map:
             self.address_categories_map = RefDataMapping(
-                *categories_shared_args, address_categories_map, "value", Blurbs.CategoriesMapping
+                *categories_shared_args, address_categories_map, "value", "CategoriesMapping"
             )
         else:
             self.address_categories_map = None
 
         if email_categories_map:
             self.email_categories_map = RefDataMapping(
-                *categories_shared_args, email_categories_map, "value", Blurbs.CategoriesMapping
+                *categories_shared_args, email_categories_map, "value", "CategoriesMapping"
             )
         else:
             self.email_categories_map = None
 
         if phone_categories_map:
             self.phone_categories_map = RefDataMapping(
-                *categories_shared_args, phone_categories_map, "value", Blurbs.CategoriesMapping
+                *categories_shared_args, phone_categories_map, "value", "CategoriesMapping"
             )
         else:
             self.phone_categories_map = None
@@ -160,7 +159,7 @@ def set_up_reference_data_mapping(
                 "organizationTypes",
                 organization_types_map,
                 "name",
-                Blurbs.OrganizationTypeMapping,
+                "OrganizationTypeMapping",
             )
         else:
             self.organization_types_map = None
diff --git a/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py b/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py
index 9712f916..faa399df 100644
--- a/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py
+++ b/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py
@@ -15,11 +15,11 @@ def __init__(
         array_name,
         the_map,
         key_type,
-        blurb,
+        blurb_id,
     ):
         self.name = array_name
         self.cache: dict = {}
-        self.blurb = blurb
+        self.blurb_id = blurb_id
         logging.info("%s reference data mapping. Initializing", self.name)
         logging.info("Fetching %s reference data from FOLIO", self.name)
         self.ref_data = list(folio_client.folio_get_all(ref_data_path, array_name, "", 1000))
diff --git a/src/folio_migration_tools/mapping_file_transformation/user_mapper.py b/src/folio_migration_tools/mapping_file_transformation/user_mapper.py
index 87f1a5a6..5fe02c0d 100644
--- a/src/folio_migration_tools/mapping_file_transformation/user_mapper.py
+++ b/src/folio_migration_tools/mapping_file_transformation/user_mapper.py
@@ -2,6 +2,7 @@
 import json
 import logging
 import sys
+import i18n
 
 from dateutil.parser import parse
 from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -16,7 +17,6 @@
 from folio_migration_tools.mapping_file_transformation.ref_data_mapping import (
     RefDataMapping,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class UserMapper(MappingFileMapperBase):
@@ -168,8 +168,12 @@ def get_parsed_date(self, mapped_value, folio_prop_name: str):
         except Exception as ee:
             v = mapped_value
             logging.error(f"{folio_prop_name} {v} could not be parsed: {ee}")
-            fmt_string = f"Parsing error! {folio_prop_name}: {v}. The empty string was returned"
-            self.migration_report.add(Blurbs.DateTimeConversions, fmt_string)
+            fmt_string = i18n.t(
+                "Parsing error! %{prop_name}: %{value}. The empty string was returned",
+                prop_name=folio_prop_name,
+                value=v,
+            )
+            self.migration_report.add("DateTimeConversions", fmt_string)
             return ""
 
     def setup_groups_mapping(self, groups_map):
@@ -180,7 +184,7 @@ def setup_groups_mapping(self, groups_map):
                 "usergroups",
                 groups_map,
                 "group",
-                Blurbs.UserGroupMapping,
+                "UserGroupMapping",
             )
         else:
             self.groups_mapping = None
@@ -193,7 +197,7 @@ def setup_departments_mapping(self, departments_mapping):
                 "departments",
                 departments_mapping,
                 "name",
-                Blurbs.DepartmentsMapping,
+                "DepartmentsMapping",
             )
         else:
             self.departments_mapping = None
diff --git a/src/folio_migration_tools/marc_rules_transformation/conditions.py b/src/folio_migration_tools/marc_rules_transformation/conditions.py
index 85d78c24..09cb1652 100644
--- a/src/folio_migration_tools/marc_rules_transformation/conditions.py
+++ b/src/folio_migration_tools/marc_rules_transformation/conditions.py
@@ -1,5 +1,6 @@
 import logging
 import re
+import i18n
 
 import pymarc
 from folioclient import FolioClient
@@ -13,7 +14,6 @@
 from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
     RulesMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 # flake8: noqa: s
 
@@ -189,7 +189,7 @@ def condition_set_contributor_type_id_by_code_or_name(
             )
             if not t:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
+                    "ContributorTypeMapping",
                     (
                         f'Mapping failed for ${contributor_code_subfield} "{subfield}" '
                         f"({normalized_subfield}) "
@@ -202,10 +202,14 @@ def condition_set_contributor_type_id_by_code_or_name(
                 )
             else:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
+                    "ContributorTypeMapping",
                     (
-                        f"Contributor type code {t[1]} found for ${contributor_code_subfield} "
-                        f'"{subfield}" ({normalized_subfield}))'
+                        i18n.t(
+                            'Contributor type code "%{code}" found for $%{code_subfield}',
+                            code=t[1],
+                            code_subfield=contributor_code_subfield,
+                        )
+                        + f' "{subfield}" ({normalized_subfield}))'
                     ),
                 )
                 return t[0]
@@ -219,7 +223,7 @@ def condition_set_contributor_type_id_by_code_or_name(
 
             if not t:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
+                    "ContributorTypeMapping",
                     (
                         f"Mapping failed for {marc_field.tag} ${contributor_name_subfield} "
                         f"{subfield} (Normalized: {normalized_subfield}) "
@@ -232,7 +236,7 @@ def condition_set_contributor_type_id_by_code_or_name(
                 )
             else:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
+                    "ContributorTypeMapping",
                     (
                         f"Contributor type name {t[1]} found for {marc_field.tag} "
                         f"${contributor_name_subfield} {normalized_subfield} ({subfield}) "
@@ -242,7 +246,7 @@ def condition_set_contributor_type_id_by_code_or_name(
         return ""
 
     def condition_set_holdings_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
-        self.mapper.migration_report.add(Blurbs.HoldingsTypeMapping, "Condition in rules hit")
+        self.mapper.migration_report.add("HoldingsTypeMapping", i18n.t("Condition in rules hit"))
         return ""
 
     def condition_concat_subfields_by_name(
@@ -266,8 +270,10 @@ def condition_get_value_if_subfield_is_empty(
         if value.strip():
             return value.strip()
         self.mapper.migration_report.add(
-            Blurbs.AddedValueFromParameter,
-            f"Tag: {marc_field.tag}. Added value: {parameter['value']}",
+            "AddedValueFromParameter",
+            i18n.t(
+                "Tag: %{tag}. Added value: %{value}", tag=marc_field.tag, value=parameter["value"]
+            ),
         )
         return parameter["value"]
 
@@ -288,14 +294,14 @@ def condition_set_instance_format_id(
                 self.folio.instance_formats, "instance_formats_code", value
             )
             self.mapper.migration_report.add(
-                Blurbs.InstanceFormat,
-                f'Successful match  - "{value}"->{t[1]}',
+                "InstanceFormat",
+                i18n.t("Successful match") + f'  - "{value}"->{t[1]}',
             )
             return t[0]
         except Exception:
             self.mapper.migration_report.add(
-                Blurbs.InstanceFormat,
-                f'Code from 338$b NOT found in FOLIO: "{value}"',
+                "InstanceFormat",
+                i18n.t('Code from 338$b NOT found in FOLIO: "%{value}"', value=value),
             )
 
             return ""
@@ -332,7 +338,7 @@ def condition_set_publisher_role(self, legacy_id, value, parameter, marc_field:
         }
         role = roles.get(marc_field.indicator2, "")
         self.mapper.migration_report.add(
-            Blurbs.MappedPublisherRoleFromIndicator2,
+            "MappedPublisherRoleFromIndicator2",
             f"{marc_field.tag} ind2 {marc_field.indicator2}->{role}",
         )
         return role
@@ -354,7 +360,7 @@ def condition_set_identifier_type_id_by_value(
                     parameter["names"][0],
                 )
             self.mapper.migration_report.add(
-                Blurbs.MappedIdentifierTypes, f"{marc_field.tag} -> {t[1]}"
+                "MappedIdentifierTypes", f"{marc_field.tag} -> {t[1]}"
             )
             return t[0]
         identifier_type = next(
@@ -368,12 +374,12 @@ def condition_set_identifier_type_id_by_value(
             ),
             None,
         )
-        self.mapper.migration_report.add(Blurbs.MappedIdentifierTypes, identifier_type["name"])
+        self.mapper.migration_report.add("MappedIdentifierTypes", identifier_type["name"])
         my_id = identifier_type["id"]
         if not my_id:
             raise TransformationFieldMappingError(
                 legacy_id,
-                f"no matching identifier_types in {parameter['names']}",
+                i18n.t("no matching identifier_types in %{names}", names=parameter["names"]),
                 marc_field,
             )
         return my_id
@@ -382,7 +388,7 @@ def condition_set_holding_note_type_id_by_name(
         self, legacy_id, value, parameter, marc_field: field.Field
     ):
         self.mapper.migration_report.add(
-            Blurbs.Exceptions,
+            "Exceptions",
             (
                 "Condition set_holding_note_type_id_by_name is deprecated. "
                 "Use set_holdings_note_type_id instead"
@@ -397,7 +403,7 @@ def condition_set_holdings_note_type_id(
             t = self.get_ref_data_tuple_by_name(
                 self.folio.holding_note_types, "holding_note_types", parameter["name"]
             )
-            self.mapper.migration_report.add(Blurbs.MappedNoteTypes, t[1])
+            self.mapper.migration_report.add("MappedNoteTypes", t[1])
             return t[0]
         except Exception as ee:
             logging.error(ee)
@@ -415,7 +421,7 @@ def condition_set_authority_note_type_id(
             t = self.get_ref_data_tuple_by_name(
                 self.authority_note_types, "authority_note_types", parameter["name"]
             )
-            self.mapper.migration_report.add(Blurbs.MappedNoteTypes, t[1])
+            self.mapper.migration_report.add("MappedNoteTypes", t[1])
             return t[0]
         except Exception as ee:
             logging.error(ee)
@@ -433,7 +439,7 @@ def condition_set_classification_type_id(
             t = self.get_ref_data_tuple_by_name(
                 self.folio.class_types, "class_types", parameter["name"]
             )
-            self.mapper.migration_report.add(Blurbs.MappedClassificationTypes, t[1])
+            self.mapper.migration_report.add("MappedClassificationTypes", t[1])
             return t[0]
         except Exception:
             raise TransformationRecordFailedError(
@@ -449,7 +455,7 @@ def condition_char_select(self, legacy_id, value, parameter, marc_field: field.F
     def condition_set_receipt_status(self, legacy_id, value, parameter, marc_field: field.Field):
         if len(value) < 7:
             self.mapper.migration_report.add(
-                Blurbs.ReceiptStatusMapping, f"008 is too short: {value}"
+                "ReceiptStatusMapping", i18n.t("008 is too short") + f": {value}"
             )
             return ""
         try:
@@ -464,13 +470,16 @@ def condition_set_receipt_status(self, legacy_id, value, parameter, marc_field:
             }
             mapped_value = status_map[value[6]]
             self.mapper.migration_report.add(
-                Blurbs.ReceiptStatusMapping, f"{value[6]} mapped to {mapped_value}"
+                "ReceiptStatusMapping",
+                i18n.t(
+                    "%{value} mapped to %{mapped_value}", value=value[6], mapped_value=mapped_value
+                ),
             )
 
             return
         except Exception:
             self.mapper.migration_report.add(
-                Blurbs.ReceiptStatusMapping, f"{value[6]} not found in map."
+                "ReceiptStatusMapping", i18n.t("%{value} not found in map.", value=value)
             )
             return "Unknown"
 
@@ -482,7 +491,7 @@ def condition_set_identifier_type_id_by_name(
                 self.folio.identifier_types, "identifier_types", parameter["name"]
             )
             self.mapper.migration_report.add(
-                Blurbs.MappedIdentifierTypes, f"{marc_field.tag} -> {t[1]}"
+                "MappedIdentifierTypes", f"{marc_field.tag} -> {t[1]}"
             )
             return t[0]
         except Exception as ee:
@@ -502,13 +511,11 @@ def condition_set_contributor_name_type_id(
                 self.folio.contrib_name_types, "contrib_name_types", parameter["name"]
             )
             self.mapper.migration_report.add(
-                Blurbs.MappedContributorNameTypes, f"{marc_field.tag} -> {t[1]}"
+                "MappedContributorNameTypes", f"{marc_field.tag} -> {t[1]}"
             )
             return t[0]
         except Exception:
-            self.mapper.migration_report.add(
-                Blurbs.UnmappedContributorNameTypes, parameter["name"]
-            )
+            self.mapper.migration_report.add("UnmappedContributorNameTypes", parameter["name"])
             return self.default_contributor_name_type
 
     def condition_set_note_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
@@ -517,7 +524,7 @@ def condition_set_note_type_id(self, legacy_id, value, parameter, marc_field: fi
                 self.folio.instance_note_types, "instance_not_types", parameter["name"]
             )
             self.mapper.migration_report.add(
-                Blurbs.MappedNoteTypes,
+                "MappedNoteTypes",
                 f"{marc_field.tag} ({parameter.get('name', '')}) -> {t[1]}",
             )
             return t[0]
@@ -534,8 +541,13 @@ def condition_set_contributor_type_id(
             )
             if not t:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
-                    f'Mapping failed for $4 "{subfield}" ({normalized_subfield}) ',
+                    "ContributorTypeMapping",
+                    i18n.t(
+                        'Mapping failed for %{tag} "%{subfield}" (%{normalized_subfield})',
+                        tag="$4",
+                        subfield=subfield,
+                        normalized_subfield=normalized_subfield,
+                    ),
                 )
                 Helper.log_data_issue(
                     legacy_id,
@@ -544,8 +556,14 @@ def condition_set_contributor_type_id(
                 )
             else:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
-                    f'Contributor type code {t[1]} found for $4 "{subfield}" ({normalized_subfield}))',
+                    "ContributorTypeMapping",
+                    i18n.t(
+                        'Contributor type code "%{code}" found for $%{code_subfield}',
+                        code=t[1],
+                        code_subfield="4",
+                        normalized_subfield=normalized_subfield,
+                    )
+                    + f' "%{subfield}" (%{normalized_subfield}))',
                 )
                 return t[0]
         subfield_code = "j" if marc_field.tag in ["111", "711"] else "e"
@@ -557,8 +575,13 @@ def condition_set_contributor_type_id(
 
             if not t:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
-                    f"Mapping failed for {marc_field.tag} $e {subfield} (Normalized: {normalized_subfield}) ",
+                    "ContributorTypeMapping",
+                    i18n.t(
+                        'Mapping failed for %{tag} "%{subfield}" (Normalized: %{normalized_subfield})',
+                        tag=f"{marc_field.tag} $e",
+                        subfield=subfield,
+                        normalized_subfield=normalized_subfield,
+                    ),
                 )
                 Helper.log_data_issue(
                     legacy_id,
@@ -567,8 +590,13 @@ def condition_set_contributor_type_id(
                 )
             else:
                 self.mapper.migration_report.add(
-                    Blurbs.ContributorTypeMapping,
-                    f"Contributor type name {t[1]} found for {marc_field.tag} $e {normalized_subfield} ({subfield}) ",
+                    "ContributorTypeMapping",
+                    i18n.t(
+                        "Contributor type name %{name} found for %{tag}",
+                        name=t[1],
+                        tag=marc_field.tag,
+                    )
+                    + f" $e {normalized_subfield} ({subfield}) ",
                 )
                 return t[0]
         return self.default_contributor_type["id"]
@@ -580,7 +608,7 @@ def condition_set_call_number_type_by_indicator(
         self, legacy_id, value, parameter, marc_field: pymarc.Field
     ):
         self.mapper.migration_report.add(
-            Blurbs.Exceptions,
+            "Exceptions",
             (
                 "Condition set_call_number_type_by_indicator is deprecated. "
                 "Change to set_call_number_type_id"
@@ -606,8 +634,8 @@ def condition_set_call_number_type_id(
         # CallNumber type specified in $2. This needs further mapping
         if marc_field.indicator1 == "7" and "2" in marc_field:
             self.mapper.migration_report.add(
-                Blurbs.CallNumberTypeMapping,
-                f"Unhandled call number type in $2 (ind1 == 7) {marc_field['2']}",
+                "CallNumberTypeMapping",
+                i18n.t("Unhandled call number type in $2 (ind1 == 7)" + str(marc_field["2"])),
             )
             return self.default_call_number_type["id"]
 
@@ -615,10 +643,13 @@ def condition_set_call_number_type_id(
         call_number_type_name_temp = first_level_map.get(marc_field.indicator1, "")
         if not call_number_type_name_temp:
             self.mapper.migration_report.add(
-                Blurbs.CallNumberTypeMapping,
+                "CallNumberTypeMapping",
                 (
-                    f'Unhandled call number type in ind1: "{marc_field.indicator1}". '
-                    f' Returning default Callnumber type: {self.default_call_number_type["name"]}'
+                    i18n.t(
+                        'Unhandled call number type in ind1: "%{ind1}".\n Returning default Callnumber type: %{type}',
+                        ind1=marc_field.indicator1,
+                        type=self.default_call_number_type["name"],
+                    )
                 ),
             )
             return self.default_call_number_type["id"]
@@ -627,13 +658,13 @@ def condition_set_call_number_type_id(
         )
         if t:
             self.mapper.migration_report.add(
-                Blurbs.CallNumberTypeMapping,
-                f"Mapped from Indicator 1 {marc_field.indicator1} -> {t[1]}",
+                "CallNumberTypeMapping",
+                i18n.t("Mapped from Indicator 1") + f" {marc_field.indicator1} -> {t[1]}",
             )
             return t[0]
 
         self.mapper.migration_report.add(
-            Blurbs.CallNumberTypeMapping,
+            "CallNumberTypeMapping",
             (
                 "Mapping failed. Setting default CallNumber type: "
                 f'{self.default_call_number_type["name"]}'
@@ -660,7 +691,7 @@ def condition_set_alternative_title_type_id(self, legacy_id, value, parameter, m
             t = self.get_ref_data_tuple_by_name(
                 self.folio.alt_title_types, "alt_title_types", parameter["name"]
             )
-            self.mapper.migration_report.add(Blurbs.MappedAlternativeTitleTypes, t[1])
+            self.mapper.migration_report.add("MappedAlternativeTitleTypes", t[1])
             return t[0]
         except Exception:
             raise TransformationProcessError(
@@ -672,7 +703,7 @@ def condition_set_location_id_by_code(
         self, legacy_id, value, parameter, marc_field: field.Field
     ):
         self.mapper.migration_report.add(
-            Blurbs.Exceptions,
+            "Exceptions",
             (
                 "set_location_id_by_code condition used in rules. "
                 "Deprecated condition. Switch to set_permanent_location_id"
@@ -720,18 +751,18 @@ def condition_set_permanent_location_id(
             mapped_code = self.ref_data_dicts["legacy_locations"].get("*", "").strip()
             if mapped_code:
                 self.mapper.migration_report.add(
-                    Blurbs.LocationMapping, f"Fallback mapping: {value}->{mapped_code}"
+                    "LocationMapping", i18n.t("Fallback mapping") + f": {value}->{mapped_code}"
                 )
         # Get the FOLIO UUID for the code and return it
         t = self.get_ref_data_tuple_by_code(self.folio.locations, "locations", mapped_code)
         if not t:
-            self.mapper.migration_report.add(Blurbs.LocationMapping, f"Unmapped code: '{value}'")
+            self.mapper.migration_report.add(
+                "LocationMapping", i18n.t("Unmapped code") + f": '{value}'"
+            )
             raise TransformationRecordFailedError(
                 legacy_id, "Could not map location from legacy code", value
             )
-        self.mapper.migration_report.add(
-            Blurbs.LocationMapping, f"'{value}' ({mapped_code}) -> {t[1]}"
-        )
+        self.mapper.migration_report.add("LocationMapping", f"'{value}' ({mapped_code}) -> {t[1]}")
         return t[0]
 
     def get_ref_data_tuple_by_code(self, ref_data, ref_name, code):
@@ -758,7 +789,7 @@ def condition_remove_substring(self, legacy_id, value, parameter, marc_field: fi
     def condition_set_instance_type_id(self, legacy_id, value, parameter, marc_field: field.Field):
         if marc_field.tag not in ["008", "336"]:
             self.mapper.migration_report.add(
-                Blurbs.InstanceTypeMapping,
+                "InstanceTypeMapping",
                 (
                     f"Unhandled MARC tag {marc_field.tag}. Instance Type ID is only mapped "
                     "from 336 "
@@ -790,7 +821,7 @@ def _extracted_from_condition_set_electronic_access_relations_id_2(self, arg0, m
             name,
         )
 
-        self.mapper.migration_report.add(Blurbs.MappedElectronicRelationshipTypes, t[1])
+        self.mapper.migration_report.add("MappedElectronicRelationshipTypes", t[1])
 
         return t[0]
 
@@ -801,8 +832,10 @@ def condition_set_note_staff_only_via_indicator(
         # https://www.loc.gov/marc/bibliographic/bd541.html
         ind1 = marc_field.indicator1
         self.mapper.migration_report.add(
-            Blurbs.StaffOnlyViaIndicator,
-            f"{marc_field.tag} indicator1: {ind1} (1 is public, all other values are Staff only)",
+            "StaffOnlyViaIndicator",
+            f"{marc_field.tag} indicator1: {ind1} ("
+            + i18n.t("1 is public, all other values are Staff only")
+            + ")",
         )
         if ind1 != "1":
             return "true"
diff --git a/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py b/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py
index 0aa71a56..5293a42b 100644
--- a/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py
+++ b/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py
@@ -2,6 +2,7 @@
 import contextlib
 import logging
 import re
+import i18n
 from typing import List
 
 from pymarc import Field
@@ -48,7 +49,11 @@ def get_holdings_statements(
             if "8" not in pattern_field:
                 raise TransformationFieldMappingError(
                     legacy_ids,
-                    f"{pattern_tag} subfield 8 not in field",
+                    i18n.t(
+                        "%{tag} subfield %{subfield} not in field",
+                        tag=pattern_tag,
+                        subfield="8",
+                    ),
                     pattern_field,
                 )
             linked_value_fields = [
@@ -154,7 +159,9 @@ def get_textual_statements(
             if "a" not in codes and "z" not in codes and "x" not in codes:
                 raise TransformationFieldMappingError(
                     legacy_ids,
-                    f"{field_textual} subfields a, x, and z missing from field",
+                    i18n.t(
+                        "%{field} subfields a, x, and z missing from field", field=field_textual
+                    ),
                     f,
                 )
             if not (
@@ -164,7 +171,7 @@ def get_textual_statements(
             ):
                 raise TransformationFieldMappingError(
                     legacy_ids,
-                    f"{field_textual} a,x and z are all empty",
+                    i18n.t("%{field} a,x and z are all empty", field=field_textual),
                     f,
                 )
             return_dict["statements"].append(
diff --git a/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py b/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py
index 4dbcddd9..bb99becf 100644
--- a/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py
+++ b/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py
@@ -1,5 +1,6 @@
 import json
 import logging
+import i18n
 from typing import Set
 
 import httpx
@@ -13,7 +14,6 @@
 from folio_migration_tools.helper import Helper
 from folio_migration_tools.library_configuration import HridHandling
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class HRIDHandler:
@@ -78,7 +78,7 @@ def generate_enumerated_hrid(
             marc_record, legacy_ids, self.migration_report, self.deactivate035_from001
         )
         marc_record.add_ordered_field(new_001)
-        self.migration_report.add(Blurbs.HridHandling, "Created HRID using default settings")
+        self.migration_report.add("HridHandling", i18n.t("Created HRID using default settings"))
 
     def enumerate_hrid(self, marc_record):
         return self.handling == HridHandling.default or "001" not in marc_record
@@ -115,10 +115,12 @@ def handle_035_generation(
         try:
             f_001 = marc_record["001"].value()
             f_003 = marc_record["003"].value().strip() if "003" in marc_record else ""
-            migration_report.add(Blurbs.HridHandling, f'Values in 003: {f_003 or "Empty"}')
+            migration_report.add(
+                "HridHandling", i18n.t("Values in %{field}", field="003") + f': {f_003 or "Empty"}'
+            )
 
             if deactivate035_from001:
-                migration_report.add(Blurbs.HridHandling, "035 generation from 001 turned off")
+                migration_report.add("HridHandling", i18n.t("035 generation from 001 turned off"))
             else:
                 str_035 = f"({f_003}){f_001}" if f_003 else f"{f_001}"
                 new_035 = Field(
@@ -127,17 +129,17 @@ def handle_035_generation(
                     subfields=[Subfield(code="a", value=str_035)],
                 )
                 marc_record.add_ordered_field(new_035)
-                migration_report.add(Blurbs.HridHandling, "Added 035 from 001")
+                migration_report.add("HridHandling", i18n.t("Added 035 from 001"))
             if remove_001:
                 marc_record.remove_fields("001")
 
         except Exception:
             if "001" in marc_record:
-                s = "Failed to create 035 from 001"
-                migration_report.add(Blurbs.HridHandling, s)
+                s = i18n.n("Failed to create %{to} from %{fro}", to="001", fro="035")
+                migration_report.add("HridHandling", s)
                 Helper.log_data_issue(legacy_ids, s, marc_record["001"])
             else:
-                migration_report.add(Blurbs.HridHandling, "Legacy bib records without 001")
+                migration_report.add("HridHandling", i18n.t("Legacy bib records without 001"))
 
     def hrids_not_updated(self):
         return (
@@ -176,7 +178,9 @@ def reset_instance_hrid_counter(self):
         logging.info("Resetting Instances HRID settings to 1")
         self.instance_hrid_counter = 1
         self.migration_report.set(
-            Blurbs.GeneralStatistics, "Instances HRID starting number", self.instance_hrid_counter
+            "GeneralStatistics",
+            i18n.t("Instances HRID starting number"),
+            self.instance_hrid_counter,
         )
         self.store_hrid_settings()
 
@@ -184,7 +188,7 @@ def reset_holdings_hrid_counter(self):
         logging.info("Resetting Holdings HRID settings to 1")
         self.holdings_hrid_counter = 1
         self.migration_report.set(
-            Blurbs.GeneralStatistics, "Holdings HRID starting number", self.holdings_hrid_counter
+            "GeneralStatistics", "Holdings HRID starting number", self.holdings_hrid_counter
         )
         self.store_hrid_settings()
 
@@ -192,7 +196,7 @@ def reset_item_hrid_counter(self):
         logging.info("Resetting Items HRID settings to 1")
         self.items_hrid_counter = 1
         self.migration_report.set(
-            Blurbs.GeneralStatistics, "Items HRID starting number", self.items_hrid_counter
+            "GeneralStatistics", "Items HRID starting number", self.items_hrid_counter
         )
         self.store_hrid_settings()
 
@@ -206,9 +210,10 @@ def preserve_001_as_hrid(
         value = marc_record["001"].value()
         if value in self.unique_001s:
             self.migration_report.add(
-                Blurbs.HridHandling,
-                "Duplicate 001. Creating HRID instead. "
-                "Previous 001 will be stored in a new 035 field",
+                "HridHandling",
+                i18n.t(
+                    "Duplicate 001. Creating HRID instead.\n Previous 001 will be stored in a new 035 field"
+                ),
             )
             self.handle_035_generation(
                 marc_record, legacy_ids, self.migration_report, self.deactivate035_from001
@@ -225,4 +230,4 @@ def preserve_001_as_hrid(
         else:
             self.unique_001s.add(value)
             folio_record["hrid"] = value
-            self.migration_report.add(Blurbs.HridHandling, "Took HRID from 001")
+            self.migration_report.add("HridHandling", i18n.t("Took HRID from 001"))
diff --git a/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py b/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py
index 34f155dc..6163fee7 100644
--- a/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py
+++ b/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py
@@ -3,6 +3,7 @@
 import time
 import traceback
 from typing import List
+import i18n
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
 from pymarc import Field
@@ -19,7 +20,6 @@
     RulesMapperBase,
 )
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class MarcFileProcessor:
@@ -82,7 +82,7 @@ def process_record(self, idx: int, marc_record: Record, file_def: FileDefinition
                     )
                 Helper.write_to_file(self.created_objects_file, folio_rec)
                 self.mapper.migration_report.add_general_statistics(
-                    "Inventory records written to disk"
+                    i18n.t("Inventory records written to disk")
                 )
                 self.exit_on_too_many_exceptions()
 
@@ -134,8 +134,8 @@ def save_srs_record(
                 )
                 marc_record["008"].data = remain
                 self.mapper.migration_report.add(
-                    Blurbs.MarcValidation,
-                    f"008 lenght invalid. '{rest}' was stripped out",
+                    "MarcValidation",
+                    i18n.t("008 length invalid. '%{rest}' was stripped out", rest=rest),
                 )
             self.add_mapped_location_code_to_record(marc_record, folio_rec)
             new_004 = Field(tag="004", data=self.parent_hrids[folio_rec["instanceId"]])
@@ -158,7 +158,7 @@ def save_srs_record(
             legacy_ids,
             file_def.discovery_suppressed,
         )
-        self.mapper.migration_report.add_general_statistics("SRS records written to disk")
+        self.mapper.migration_report.add_general_statistics(i18n.t("SRS records written to disk"))
 
     def add_mapped_location_code_to_record(self, marc_record, folio_rec):
         location_code = next(
@@ -178,10 +178,12 @@ def add_mapped_location_code_to_record(self, marc_record, folio_rec):
         while old_b := first_852.delete_subfield("b"):
             first_852.add_subfield("x", old_b, 0)
             self.mapper.migration_report.add(
-                Blurbs.LocationMapping, "Additional 852$b was moved to 852$x"
+                "LocationMapping", i18n.t("Additional 852$b was moved to 852$x")
             )
         first_852.add_subfield("b", location_code, 0)
-        self.mapper.migration_report.add(Blurbs.LocationMapping, "Set 852 to FOLIO location code")
+        self.mapper.migration_report.add(
+            "LocationMapping", i18n.t("Set 852 to FOLIO location code")
+        )
 
     def exit_on_too_many_exceptions(self):
         if (
@@ -202,9 +204,11 @@ def get_valid_folio_record_ids(
             if legacy_id not in folio_record_identifiers:
                 new_ids.add(legacy_id)
             else:
-                migration_report.add_general_statistics("Duplicate MARC record identifiers ")
+                migration_report.add_general_statistics(
+                    i18n.t("Duplicate MARC record identifiers ")
+                )
         if not any(new_ids):
-            s = "Failed records. No unique record identifiers in legacy record"
+            s = i18n.t("Failed records. No unique record identifiers in legacy record")
             migration_report.add_general_statistics(s)
             raise TransformationRecordFailedError(
                 "-".join(legacy_ids),
@@ -224,7 +228,9 @@ def wrap_up(self):
         logging.info("%s records processed", self.records_count)
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.mapper.migration_report.write_migration_report(
-                "MFHD records transformation report", report_file, self.mapper.start_datetime
+                i18n.t("MFHD records transformation report"),
+                report_file,
+                self.mapper.start_datetime,
             )
             Helper.print_mapping_report(
                 report_file,
diff --git a/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py b/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py
index 43823f00..19a24a82 100644
--- a/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py
+++ b/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py
@@ -1,5 +1,6 @@
 import logging
 import sys
+import i18n
 from io import IOBase
 from pathlib import Path
 
@@ -11,7 +12,6 @@
 from folio_migration_tools.folder_structure import FolderStructure
 from folio_migration_tools.library_configuration import FileDefinition
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class MARCReaderWrapper:
@@ -50,7 +50,7 @@ def read_records(
     ):
         for idx, record in enumerate(reader):
             processor.mapper.migration_report.add_general_statistics(
-                "Records in file before parsing"
+                i18n.t("Records in file before parsing")
             )
             try:
                 # None = Something bad happened
@@ -66,13 +66,13 @@ def read_records(
                 else:
                     MARCReaderWrapper.set_leader(record, processor.mapper.migration_report)
                     processor.mapper.migration_report.add_general_statistics(
-                        "Records successfully decoded from MARC21",
+                        i18n.t("Records successfully decoded from MARC21"),
                     )
                     processor.process_record(idx, record, source_file)
             except TransformationRecordFailedError as error:
                 error.log_it()
                 processor.mapper.migration_report.add_general_statistics(
-                    "Records that failed transformation. Check log for details",
+                    i18n.t("Records that failed transformation. Check log for details"),
                 )
             except ValueError as error:
                 logging.error(error)
@@ -82,29 +82,38 @@ def read_records(
     def set_leader(marc_record: Record, migration_report: MigrationReport):
         if marc_record.leader[9] != "a":
             migration_report.add(
-                Blurbs.LeaderManipulation,
-                f"Set leader 09 (Character coding scheme) from {marc_record.leader[9]} to a",
+                "LeaderManipulation",
+                i18n.t(
+                    "Set leader 09 (Character coding scheme) from %{field} to a",
+                    field=marc_record.leader[9],
+                ),
             )
             marc_record.leader = f"{marc_record.leader[:9]}a{marc_record.leader[10:]}"
 
         if not marc_record.leader.endswith("4500"):
             migration_report.add(
-                Blurbs.LeaderManipulation,
-                f"Set leader 20-23 from {marc_record.leader[-4:]} to 4500",
+                "LeaderManipulation",
+                i18n.t("Set leader 20-23 from %{field} to 4500", field=marc_record.leader[-4:]),
             )
             marc_record.leader = f"{marc_record.leader[:-4]}4500"
 
         if marc_record.leader[10] != "2":
             migration_report.add(
-                Blurbs.LeaderManipulation,
-                f"Set leader 10 (Indicator count) from {marc_record.leader[10]} to 2",
+                "LeaderManipulation",
+                i18n.t(
+                    "Set leader 10 (Indicator count) from %{field} to 2",
+                    field=marc_record.leader[10],
+                ),
             )
             marc_record.leader = f"{marc_record.leader[:10]}2{marc_record.leader[11:]}"
 
         if marc_record.leader[11] != "2":
             migration_report.add(
-                Blurbs.LeaderManipulation,
-                f"Set leader 10 (Subfield code count) from {marc_record.leader[11]} to 2",
+                "LeaderManipulation",
+                i18n.t(
+                    "Set leader 11 (Subfield code count) from %{record} to 2",
+                    record=marc_record.leader[11],
+                ),
             )
             marc_record.leader = f"{marc_record.leader[:11]}2{marc_record.leader[12:]}"
 
@@ -113,7 +122,7 @@ def report_failed_parsing(
     reader, source_file, failed_bibs_file, idx, migration_report: MigrationReport
 ):
     migration_report.add_general_statistics(
-        "Records with encoding errors - parsing failed",
+        i18n.t("Records with encoding errors - parsing failed"),
     )
     failed_bibs_file.write(reader.current_chunk)
     raise TransformationRecordFailedError(
diff --git a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py
index 62b2a8ed..7746f930 100644
--- a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py
+++ b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py
@@ -4,6 +4,7 @@
 import re
 import time
 import uuid
+import i18n
 from typing import List
 
 import pymarc
@@ -22,7 +23,6 @@
 from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
     RulesMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class AuthorityMapper(RulesMapperBase):
@@ -159,22 +159,26 @@ def map_source_file_and_natural_id(self, marc_record, folio_authority):
                 ):
                     natural_id = "".join(a_subfield.split())
                     source_file_id = source_file["id"]
-                    self.migration_report.add_general_statistics("naturalId mapped from 010$a")
+                    self.migration_report.add_general_statistics(
+                        i18n.t("naturalId mapped from %{fro}", fro="010$a")
+                    )
                     self.migration_report.add(
-                        Blurbs.AuthoritySourceFileMapping,
+                        "AuthoritySourceFileMapping",
                         f"{source_file['name']} -- {natural_id_prefix.group(0)} -- 010$a",
                         number=1,
                     )
                     break
         if not source_file_id:
             natural_id = "".join(marc_record["001"].data.split())
-            self.migration_report.add_general_statistics("naturalId mapped from 001")
+            self.migration_report.add_general_statistics(
+                i18n.t("naturalId mapped from %{fro}", fro="001")
+            )
             natural_id_prefix = match_prefix_patt.match(natural_id)
             if natural_id_prefix:
                 if source_file := self.source_file_mapping.get(natural_id_prefix.group(0), None):
                     source_file_id = source_file["id"]
                     self.migration_report.add(
-                        Blurbs.AuthoritySourceFileMapping,
+                        "AuthoritySourceFileMapping",
                         f"{source_file['name']} -- {natural_id_prefix.group(0)} -- 001",
                         number=1,
                     )
@@ -190,7 +194,9 @@ def setup_source_file_mapping(self):
 
     def handle_leader_17(self, marc_record, legacy_ids):
         leader_17 = marc_record.leader[17] or "Empty"
-        self.migration_report.add(Blurbs.AuthorityEncodingLevel, f"Original value: {leader_17}")
+        self.migration_report.add(
+            "AuthorityEncodingLevel", i18n.t("Original value") + f": {leader_17}"
+        )
         if leader_17 not in ["n", "o"]:
             Helper.log_data_issue(
                 legacy_ids,
@@ -198,7 +204,9 @@ def handle_leader_17(self, marc_record, legacy_ids):
                 marc_record.leader,
             )
             marc_record.leader = f"{marc_record.leader[:17]}n{marc_record.leader[18:]}"
-            self.migration_report.add(Blurbs.AuthorityEncodingLevel, f"Changed {leader_17} to n")
+            self.migration_report.add(
+                "AuthorityEncodingLevel", i18n.t("Changed %{a} to %{b}", a=leader_17, b="n")
+            )
 
     def perform_additional_parsing(
         self,
diff --git a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py
index 99905458..025f2087 100644
--- a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py
+++ b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py
@@ -4,6 +4,7 @@
 import time
 import urllib.parse
 import uuid
+import i18n
 from abc import abstractmethod
 from textwrap import wrap
 from typing import List
@@ -27,7 +28,6 @@
 from folio_migration_tools.library_configuration import LibraryConfiguration
 from folio_migration_tools.mapper_base import MapperBase
 from folio_migration_tools.marc_rules_transformation.hrid_handler import HRIDHandler
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class RulesMapperBase(MapperBase):
@@ -291,13 +291,13 @@ def process_marc_field(
     def perform_proxy_mapping(self, marc_field):
         proxy_mapping = next(iter(self.mappings.get("880", [])), [])
         if "6" not in marc_field:
-            self.migration_report.add(Blurbs.Field880Mappings, "Records without $6")
+            self.migration_report.add("Field880Mappings", i18n.t("Records without $6"))
             return None
         if not proxy_mapping or not proxy_mapping.get("fieldReplacementBy3Digits", False):
             return None
         if not marc_field["6"][:3] or len(marc_field["6"][:3]) != 3:
             self.migration_report.add(
-                Blurbs.Field880Mappings, "Records with unexpected length in $6"
+                "Field880Mappings", i18n.t("Records with unexpected length in $6")
             )
             return None
         first_three = marc_field["6"][:3]
@@ -311,21 +311,25 @@ def perform_proxy_mapping(self, marc_field):
             first_three,
         )
         self.migration_report.add(
-            Blurbs.Field880Mappings,
-            f"Source digits: {marc_field['6']} Target field: {target_field}",
+            "Field880Mappings",
+            i18n.t("Source digits")
+            + f": {marc_field['6']} "
+            + i18n.t("Target field")
+            + f": {target_field}",
         )
         mappings = self.mappings.get(target_field, {})
         if not mappings:
             self.migration_report.add(
-                Blurbs.Field880Mappings,
-                f"Mapping not set up for target field: {target_field} ({marc_field['6']})",
+                "Field880Mappings",
+                i18n.t("Mapping not set up for target field")
+                + f": {target_field} ({marc_field['6']})",
             )
         return mappings
 
     def report_marc_stats(
         self, marc_field: Field, bad_tags, legacy_ids, ignored_subsequent_fields
     ):
-        self.migration_report.add(Blurbs.Trivia, "Total number of Tags processed")
+        self.migration_report.add("Trivia", i18n.t("Total number of Tags processed"))
         self.report_source_and_links(marc_field)
         self.report_bad_tags(marc_field, bad_tags, legacy_ids)
         mapped = marc_field.tag in self.mappings
@@ -338,7 +342,8 @@ def report_source_and_links(self, marc_field: Field):
             return
         for subfield_2 in marc_field.get_subfields("2"):
             self.migration_report.add(
-                Blurbs.AuthoritySources, f"Source of heading or term: {subfield_2.split(' ')[0]}"
+                "AuthoritySources",
+                i18n.t("Source of heading or term") + f": {subfield_2.split(' ')[0]}",
             )
         for subfield_0 in marc_field.get_subfields("0"):
             code = ""
@@ -350,7 +355,7 @@ def report_source_and_links(self, marc_field: Field):
                     code = subfield_0[: subfield_0.find(url.path)]
             if code:
                 self.migration_report.add(
-                    Blurbs.AuthoritySources, f"$0 base uri or source code: {code}"
+                    "AuthoritySources", i18n.t("$0 base uri or source code") + f": {code}"
                 )
 
     def apply_rules(self, marc_field: pymarc.Field, mapping, legacy_ids):
@@ -377,7 +382,7 @@ def apply_rules(self, marc_field: pymarc.Field, mapping, legacy_ids):
         except TransformationProcessError as trpe:
             self.handle_transformation_process_error(self.parsed_records, trpe)
         except TransformationFieldMappingError as fme:
-            self.migration_report.add(Blurbs.FieldMappingErrors, fme.message)
+            self.migration_report.add("FieldMappingErrors", fme.message)
             fme.data_value = (
                 f"{fme.data_value} MARCField: {marc_field} Mapping: {json.dumps(mapping)}"
             )
@@ -389,7 +394,7 @@ def apply_rules(self, marc_field: pymarc.Field, mapping, legacy_ids):
             )
             trfe.log_it()
             self.migration_report.add_general_statistics(
-                "Records failed due to an error. See data issues log for details"
+                i18n.t("Records failed due to an error. See data issues log for details")
             )
         except Exception as exception:
             self.handle_generic_exception(self.parsed_records, exception)
@@ -400,7 +405,7 @@ def report_bad_tags(self, marc_field, bad_tags, legacy_ids):
             and marc_field.tag != "LDR"
             and marc_field.tag not in bad_tags
         ):
-            self.migration_report.add(Blurbs.NonNumericTagsInRecord, marc_field.tag)
+            self.migration_report.add("NonNumericTagsInRecord", marc_field.tag)
             message = "Non-numeric tags in records"
             Helper.log_data_issue(legacy_ids, message, marc_field.tag)
             bad_tags.add(marc_field.tag)
@@ -486,16 +491,18 @@ def add_value_to_first_level_target(self, rec, target_string, value):
             except Exception as ee:
                 Helper.log_data_issue("", f"Could not parse catalogedDate: {ee}", value)
                 self.migration_report.add(
-                    Blurbs.FieldMappingErrors, "Could not parse catalogedDate"
+                    "FieldMappingErrors", i18n.t("Could not parse catalogedDate")
                 )
         if not target_string or target_string not in sch:
             raise TransformationFieldMappingError(
                 "",
-                (
-                    f"Target string '{target_string}' not in Schema! "
-                    "Check mapping file against the schema. "
-                    f"Target type: {sch.get(target_string,{}).get('type','')} Value: {value}"
-                ),
+                i18n.t("Target string '%{string}' not in Schema!", string=target_string)
+                + i18n.t("Check mapping file against the schema.")
+                + " "
+                + i18n.t("Target type")
+                + f": {sch.get(target_string,{}).get('type','')} "
+                + i18n.t("Value")
+                + f": {value}",
                 "",
             )
 
@@ -611,7 +618,7 @@ def handle_entity_mapping(
                 )
                 pattern = " - ".join(f"{k}:'{bool(v)}'" for k, v in entity.items())
                 self.migration_report.add(
-                    Blurbs.IncompleteEntityMapping,
+                    "IncompleteEntityMapping",
                     f"{marc_field.tag} {sfs} ->>-->> {e_parent} {pattern}  ",
                 )
                 # Experimental
@@ -622,17 +629,17 @@ def handle_suppression(
     ):
         folio_record["discoverySuppress"] = file_def.discovery_suppressed
         self.migration_report.add(
-            Blurbs.Suppression,
-            f'Suppressed from discovery = {folio_record["discoverySuppress"]}',
+            "Suppression",
+            i18n.t("Suppressed from discovery") + f' = {folio_record["discoverySuppress"]}',
         )
         if not only_discovery_suppress:
             folio_record["staffSuppress"] = file_def.staff_suppressed
             self.migration_report.add(
-                Blurbs.Suppression, f'Staff suppressed = {folio_record["staffSuppress"]} '
+                "Suppression", i18n.t("Staff suppressed") + f' = {folio_record["staffSuppress"]} '
             )
 
     def create_preceding_succeeding_titles(self, entity, e_parent, identifier):
-        self.migration_report.add(Blurbs.PrecedingSuccedingTitles, f"{e_parent} created")
+        self.migration_report.add("PrecedingSuccedingTitles", f"{e_parent} " + i18n.t("created"))
         # TODO: Make these uuids deterministic
         new_entity = {
             "id": str(uuid.uuid4()),
diff --git a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py
index fffc10ff..7b5aa4ab 100644
--- a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py
+++ b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py
@@ -5,6 +5,7 @@
 import time
 import typing
 import uuid
+import i18n
 from pathlib import Path
 from typing import Generator
 from typing import List
@@ -26,7 +27,6 @@
 from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
     RulesMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class BibsRulesMapper(RulesMapperBase):
@@ -83,10 +83,12 @@ def perform_initial_preparation(self, marc_record: pymarc.Record, legacy_ids):
 
     def handle_leader_05(self, marc_record, legacy_ids):
         leader_05 = marc_record.leader[5] or "Empty"
-        self.migration_report.add(Blurbs.RecordStatus, f"Original value: {leader_05}")
+        self.migration_report.add("RecordStatus", i18n.t("Original value") + f": {leader_05}")
         if leader_05 not in ["a", "c", "d", "n", "p"]:
             marc_record.leader = f"{marc_record.leader[:5]}c{marc_record.leader[6:]}"
-            self.migration_report.add(Blurbs.RecordStatus, f"Changed {leader_05} to c")
+            self.migration_report.add(
+                "RecordStatus", i18n.t("Changed %{a} to %{b}", a=leader_05, b="c")
+            )
         if leader_05 == "d":
             Helper.log_data_issue(legacy_ids, "d in leader. Is this correct?", marc_record.leader)
 
@@ -154,11 +156,11 @@ def perform_additional_parsing(
         self.handle_suppression(folio_instance, file_def)
         self.handle_holdings(marc_record)
         if prec_titles := folio_instance.get("precedingTitles", []):
-            self.migration_report.add(Blurbs.PrecedingSuccedingTitles, f"{len(prec_titles)}")
+            self.migration_report.add("PrecedingSuccedingTitles", f"{len(prec_titles)}")
             del folio_instance["precedingTitles"]
         if succ_titles := folio_instance.get("succeedingTitles", []):
             del folio_instance["succeedingTitles"]
-            self.migration_report.add(Blurbs.PrecedingSuccedingTitles, f"{len(succ_titles)}")
+            self.migration_report.add("PrecedingSuccedingTitles", f"{len(succ_titles)}")
 
     def handle_languages(self, folio_instance, marc_record, legacy_ids):
         if "languages" in folio_instance:
@@ -190,19 +192,25 @@ def handle_holdings(self, marc_record: Record):
             )
             if f852s and not f86xs:
                 self.migration_report.add(
-                    Blurbs.HoldingsGenerationFromBibs,
-                    "Records with 852s but no 86X",
+                    "HoldingsGenerationFromBibs",
+                    i18n.t(
+                        "Records with %{has_many}s but no %{has_no}", has_many="852", has_no="86X"
+                    ),
                 )
             elif any(f852s):
                 self.migration_report.add(
-                    Blurbs.HoldingsGenerationFromBibs,
-                    "Records with both 852s and at least one 86X",
+                    "HoldingsGenerationFromBibs",
+                    i18n.t(
+                        "Records with both %{has_many}s and at least one %{has_one}",
+                        has_one="86X",
+                        has_many="852",
+                    ),
                 )
 
             elif any(f86xs):
                 self.migration_report.add(
-                    Blurbs.HoldingsGenerationFromBibs,
-                    "Records without 852s but with 86X",
+                    "HoldingsGenerationFromBibs",
+                    i18n.t("Records without %{has_no}s but with %{has}", has="86X", has_no="852"),
                 )
 
     def wrap_up(self):
@@ -225,13 +233,17 @@ def get_folio_id_by_name(f336a: str):
             )
             if match:
                 self.migration_report.add(
-                    Blurbs.RecourceTypeMapping,
-                    f"336$a - Successful matching on  {match_template} ({f336a})",
+                    "RecourceTypeMapping",
+                    "336$a - "
+                    + i18n.t("Successful matching on %{criteria}", criteria=match_template)
+                    + f" ({f336a})",
                 )
             else:
                 self.migration_report.add(
-                    Blurbs.RecourceTypeMapping,
-                    f"336$a - Unsuccessful matching on  {match_template} ({f336a})",
+                    "RecourceTypeMapping",
+                    "336$a - "
+                    + i18n.t("Unsuccessful matching on %{criteria}", criteria=match_template)
+                    + f" ({f336a})",
                 )
                 Helper.log_data_issue(
                     legacy_id,
@@ -244,7 +256,7 @@ def get_folio_id_by_name(f336a: str):
             raise TransformationProcessError("", "No instance_types setup in tenant")
 
         if "336" in marc_record and "b" not in marc_record["336"]:
-            self.migration_report.add(Blurbs.RecourceTypeMapping, "Subfield b not in 336")
+            self.migration_report.add("RecourceTypeMapping", i18n.t("Subfield b not in 336"))
             if "a" in marc_record["336"]:
                 return_id = get_folio_id_by_name(marc_record["336"]["a"])
 
@@ -258,18 +270,26 @@ def get_folio_id_by_name(f336a: str):
             )
             if not t:
                 self.migration_report.add(
-                    Blurbs.RecourceTypeMapping,
-                    f"336$b - Code {f336_b_norm} ('{f336_b}') not found in FOLIO ",
+                    "RecourceTypeMapping",
+                    "336$b - "
+                    + i18n.t(
+                        "Code %{code} ('%{code_raw}') not found in FOLIO ",
+                        code=f336_b_norm,
+                        code_raw=f336_b,
+                    ),
                 )
                 Helper.log_data_issue(
                     legacy_id,
-                    "instance type code (336$b) not found in FOLIO",
+                    i18n.t("instance type code (%{code}) not found in FOLIO", code="336$b"),
                     f336_b,
                 )
             else:
                 self.migration_report.add(
-                    Blurbs.RecourceTypeMapping,
-                    f'336$b {t[1]} mapped from {marc_record["336"]["b"]}',
+                    "RecourceTypeMapping",
+                    "336$b "
+                    + i18n.t(
+                        "%{fro} mapped from %{record}", fro=t[1], record=marc_record["336"]["b"]
+                    ),
                 )
                 return_id = t[0]
 
@@ -284,16 +304,16 @@ def get_instance_format_id_by_code(self, legacy_id: str, code: str):
         try:
             match = next(f for f in self.folio_client.instance_formats if f["code"] == code)
             self.migration_report.add(
-                Blurbs.InstanceFormat,
-                f"Successful match  - {code}->{match['name']}",
+                "InstanceFormat",
+                i18n.t("Successful match") + f"  - {code}->{match['name']}",
             )
             return match["id"]
         except Exception:
             # TODO: Distinguish between generated codes and proper 338bs
             Helper.log_data_issue(legacy_id, "Instance format Code not found in FOLIO", code)
             self.migration_report.add(
-                Blurbs.InstanceFormat,
-                f"Code '{code}' not found in FOLIO",
+                "InstanceFormat",
+                i18n.t("Code '%{code}' not found in FOLIO", code=code),
             )
             return ""
 
@@ -308,8 +328,13 @@ def get_instance_format_id_by_name(self, f337a: str, f338a: str, legacy_id: str)
                 if f["name"].lower() == match_template
             )
             self.migration_report.add(
-                Blurbs.InstanceFormat,
-                f"Successful matching on 337$a & 338$a - {match_template}->{match['name']}",
+                "InstanceFormat",
+                i18n.t(
+                    "Successful matching on %{criteria_1} and %{criteria_2}",
+                    criteria_1="337$a",
+                    criteria_2="338$a",
+                )
+                + f" - {match_template}->{match['name']}",
             )
             return match["id"]
         except Exception:
@@ -319,22 +344,27 @@ def get_instance_format_id_by_name(self, f337a: str, f338a: str, legacy_id: str)
                 match_template,
             )
             self.migration_report.add(
-                Blurbs.InstanceFormat,
-                f"Unsuccessful matching on 337$a and 338$a - {match_template}",
+                "InstanceFormat",
+                i18n.t(
+                    "Unsuccessful matching on %{criteria_1} and %{criteria_2}",
+                    criteria_1="337$a",
+                    criteria_2="338$a",
+                )
+                + f" - {match_template}",
             )
             return ""
 
     def f338_source_is_rda_carrier(self, field: pymarc.Field):
         if "2" not in field:
             self.migration_report.add(
-                Blurbs.InstanceFormat,
+                "InstanceFormat",
                 ("Instance Format not mapped from field since 338$2 is missing"),
             )
             return False
         elif field["2"].strip().startswith("rdacarrier"):
             return True
         self.migration_report.add(
-            Blurbs.InstanceFormat,
+            "InstanceFormat",
             ("InstanceFormat not mapped since 338$2 (Source) " f"is set to {field['2']}. "),
         )
         return False
@@ -343,8 +373,8 @@ def get_instance_format_ids_from_a(
         self, field_index, f_338: pymarc.Field, all_337s, legacy_id
     ):
         self.migration_report.add(
-            Blurbs.InstanceFormat,
-            "338$b is missing. Will try parse from 337$a and 338$a",
+            "InstanceFormat",
+            i18n.t("338$b is missing. Will try parse from 337$a and 338$a"),
         )
         for a in f_338.get_subfields("a"):
             corresponding_337 = all_337s[field_index] if field_index < len(all_337s) else None
@@ -373,10 +403,12 @@ def get_instance_format_ids(self, marc_record, legacy_id):
                         corresponding_337 = all_337s[fidx] if fidx < len(all_337s) else None
                         if not corresponding_337:
                             # No matching 337. No use mapping the 338
-                            s = "No corresponding 337 to 338 even though 338$b was one character"
+                            s = i18n.t(
+                                "No corresponding 337 to 338 even though 338$b was one character"
+                            )
                             Helper.log_data_issue(legacy_id, s, b)
                             self.migration_report.add(
-                                Blurbs.InstanceFormat,
+                                "InstanceFormat",
                                 s,
                             )
                         else:
@@ -387,9 +419,9 @@ def get_instance_format_ids(self, marc_record, legacy_id):
                                 else None
                             )
                             if not corresponding_b:
-                                s = "No corresponding $b in corresponding 338"
+                                s = i18n.t("No corresponding $b in corresponding 338")
                                 Helper.log_data_issue(legacy_id, s, "")
-                                self.migration_report.add(Blurbs.InstanceFormat, s)
+                                self.migration_report.add("InstanceFormat", s)
                             else:
                                 combined_code = (corresponding_b + b).strip()
                                 if len(combined_code) == 2:
@@ -416,17 +448,19 @@ def get_mode_of_issuance_id(self, marc_record: Record, legacy_id: List[str]) ->
                 "",
             )
 
-            self.migration_report.add(Blurbs.MatchedModesOfIssuanceCode, f"{name} -- {ret}")
+            self.migration_report.add("MatchedModesOfIssuanceCode", f"{name} -- {ret}")
 
             if not ret:
                 self.migration_report.add(
-                    Blurbs.MatchedModesOfIssuanceCode, f"Unmatched level: {level}"
+                    "MatchedModesOfIssuanceCode", i18n.t("Unmatched level") + f": {level}"
                 )
 
                 return self.other_mode_of_issuance_id
             return ret
         except IndexError:
-            self.migration_report.add(Blurbs.PossibleCleaningTasks, f"No Leader[7] in {legacy_id}")
+            self.migration_report.add(
+                "PossibleCleaningTasks", i18n.t("No Leader[7] in") + f" {legacy_id}"
+            )
 
             return self.other_mode_of_issuance_id
         except StopIteration as ee:
@@ -449,7 +483,7 @@ def get_languages_041(self, marc_record, legacy_id):
         subfields = "abdefghjkmn"
         for lang_tag in lang_fields:
             if "2" in lang_tag:
-                self.migration_report.add(Blurbs.LanguageCodeSources, lang_tag["2"])
+                self.migration_report.add("LanguageCodeSources", lang_tag["2"])
                 Helper.log_data_issue(
                     legacy_id, "Field with other Language code", lang_tag.value()
                 )
@@ -485,7 +519,7 @@ def get_languages(self, marc_record: Record, legacy_id: str) -> List[str]:
         languages = self.get_languages_041(marc_record, legacy_id)
         languages[self.get_languages_008(marc_record)] = None
         for lang in languages.keys():
-            self.migration_report.add(Blurbs.LanguagesInRecords, lang)
+            self.migration_report.add("LanguagesInRecords", lang)
         return list(languages.keys())
 
     def fetch_language_codes(self) -> Generator[str, None, None]:
@@ -526,7 +560,7 @@ def filter_langs(
                 m = "Unrecognized language codes in record"
                 Helper.log_data_issue(index_or_legacy_id, m, language_value)
                 self.migration_report.add(
-                    Blurbs.UnrecognizedLanguageCodes,
+                    "UnrecognizedLanguageCodes",
                     f"{m}: {language_value}",
                 )
 
@@ -561,12 +595,16 @@ def get_legacy_ids(self, marc_record: Record, idx: int) -> List[str]:
     def get_aleph_bib_id(self, marc_record: Record):
         res = {f["b"].strip(): None for f in marc_record.get_fields("998") if "b" in f}
         if any(res):
-            self.migration_report.add_general_statistics("legacy id from 998$b")
+            self.migration_report.add_general_statistics(
+                i18n.t("legacy id from %{fro}", fro="998$b")
+            )
             return list(res)
         else:
             try:
                 ret = [marc_record["001"].format_field().strip()]
-                self.migration_report.add_general_statistics("legacy id from 001")
+                self.migration_report.add_general_statistics(
+                    i18n.t("legacy id from %{fro}", fro="001")
+                )
                 return ret
             except Exception as e:
                 raise TransformationRecordFailedError(
diff --git a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py
index 9de35824..cb31be3a 100644
--- a/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py
+++ b/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py
@@ -2,6 +2,7 @@
 import json
 import logging
 from typing import List
+import i18n
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
 from folio_uuid.folio_uuid import FolioUUID
@@ -24,7 +25,6 @@
 from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
     RulesMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 class RulesMapperHoldings(RulesMapperBase):
@@ -108,7 +108,7 @@ def parse_record(
         self.print_progress()
         folio_holding = self.perform_initial_preparation(marc_record, legacy_ids)
 
-        self.migration_report.add(Blurbs.RecordStatus, marc_record.leader[5])
+        self.migration_report.add("RecordStatus", marc_record.leader[5])
         ignored_subsequent_fields: set = set()
         num_852s = 0
         for marc_field in marc_record:
@@ -223,7 +223,7 @@ def process_marc_field(
             ignored_subsequent_fields (_type_): _description_
             index_or_legacy_ids (_type_): _description_
         """
-        self.migration_report.add(Blurbs.Trivia, "Total number of Tags processed")
+        self.migration_report.add("Trivia", i18n.t("Total number of Tags processed"))
         if marc_field.tag not in self.mappings:
             self.report_legacy_mapping(marc_field.tag, True, False)
         elif marc_field.tag not in ignored_subsequent_fields:
@@ -312,12 +312,10 @@ def parse_coded_holdings_statements(
                 if res["statements"]:
                     folio_holding[key] = res["statements"]
                 for mr in res["migration_report"]:
-                    self.migration_report.add(
-                        Blurbs.HoldingsStatementsParsing, f"{mr[0]} -- {mr[1]}"
-                    )
+                    self.migration_report.add("HoldingsStatementsParsing", f"{mr[0]} -- {mr[1]}")
             except TransformationFieldMappingError as tfme:
                 Helper.log_data_issue(tfme.index_or_id, tfme.message, tfme.data_value)
-                self.migration_report.add(Blurbs.FieldMappingErrors, tfme.message)
+                self.migration_report.add("FieldMappingErrors", tfme.message)
 
     def wrap_up(self):
         logging.info("Mapper wrapping up")
@@ -340,8 +338,13 @@ def set_holdings_type(self, marc_record: Record, folio_holding, legacy_ids: List
         # type = type_map.get(ldr06, "Unknown")
         if folio_holding.get("holdingsTypeId", ""):
             self.migration_report.add(
-                Blurbs.HoldingsTypeMapping,
-                f"Already set to {folio_holding.get('holdingsTypeId')}. LDR[06] was {ldr06}",
+                "HoldingsTypeMapping",
+                i18n.t(
+                    "Already set to %{value}. %{leader_key} was %{leader}",
+                    value=folio_holding.get("holdingsTypeId"),
+                    leader_key="LDR[06]",
+                    leader=ldr06,
+                ),
             )
         else:
             holdings_type = self.conditions.holdings_type_map.get(ldr06, "")
@@ -350,14 +353,14 @@ def set_holdings_type(self, marc_record: Record, folio_holding, legacy_ids: List
             ):
                 folio_holding["holdingsTypeId"] = t[0]
                 self.migration_report.add(
-                    Blurbs.HoldingsTypeMapping,
+                    "HoldingsTypeMapping",
                     f"{ldr06} -> {holdings_type} -> {t[1]} ({t[0]}",
                 )
                 if holdings_type == "Unknown":
                     Helper.log_data_issue(
                         legacy_ids,
                         (
-                            f"{Blurbs.HoldingsTypeMapping[0]} is 'unknown'. "
+                            i18n.t("blurbs.HoldingsTypeMapping.title") + " is 'unknown'. "
                             "(leader 06 is set to 'u') Check if this is correct"
                         ),
                         ldr06,
@@ -370,12 +373,17 @@ def set_holdings_type(self, marc_record: Record, folio_holding, legacy_ids: List
                     )
                 folio_holding["holdingsTypeId"] = self.fallback_holdings_type_id
                 self.migration_report.add(
-                    Blurbs.HoldingsTypeMapping,
-                    f"A Unmapped {ldr06} -> {holdings_type} -> Unmapped",
+                    "HoldingsTypeMapping",
+                    i18n.t("An Unmapped")
+                    + f" {ldr06} -> {holdings_type} -> "
+                    + i18n.t("Unmapped"),
                 )
                 Helper.log_data_issue(
                     legacy_ids,
-                    (f"{Blurbs.HoldingsTypeMapping[0]}. leader 06 was unmapped."),
+                    (
+                        i18n.t("blurbs.HoldingsTypeMapping.title", locale="en")
+                        + ". leader 06 was unmapped."
+                    ),
                     ldr06,
                 )
 
diff --git a/src/folio_migration_tools/migration_report.py b/src/folio_migration_tools/migration_report.py
index 527a5ecd..dbadb6e9 100644
--- a/src/folio_migration_tools/migration_report.py
+++ b/src/folio_migration_tools/migration_report.py
@@ -1,9 +1,8 @@
 import logging
+import i18n
 from datetime import datetime
 from datetime import timezone
 
-from folio_migration_tools.report_blurbs import Blurbs
-
 
 class MigrationReport:
     """Class responsible for handling the migration report"""
@@ -12,23 +11,23 @@ def __init__(self):
         self.report = {}
         self.stats = {}
 
-    def add(self, blurb_tuple: tuple, measure_to_add, number=1):
+    def add(self, blurb_id, measure_to_add, number=1):
         """Add section header and values to migration report.
 
         Args:
-            blurb_tuple (tuple): _description_
+            blurb_id (string): ID of Blurb in translations file
             measure_to_add (_type_): _description_
             number (int, optional): _description_. Defaults to 1.
         """
         try:
-            self.report[blurb_tuple[0]][measure_to_add] += number
+            self.report[blurb_id][measure_to_add] += number
         except KeyError:
-            if blurb_tuple[0] not in self.report:
-                self.report[blurb_tuple[0]] = {"blurb_tuple": blurb_tuple}
-            if measure_to_add not in self.report[blurb_tuple[0]]:
-                self.report[blurb_tuple[0]][measure_to_add] = number
+            if blurb_id not in self.report:
+                self.report[blurb_id] = {"blurb_id": blurb_id}
+            if measure_to_add not in self.report[blurb_id]:
+                self.report[blurb_id][measure_to_add] = number
 
-    def set(self, blurb, measure_to_add: str, number: int):
+    def set(self, blurb_id, measure_to_add: str, number: int):
         """Set a section value  to a specific number
 
         Args:
@@ -36,9 +35,9 @@ def set(self, blurb, measure_to_add: str, number: int):
             measure_to_add (str): _description_
             number (int): _description_
         """
-        if blurb[0] not in self.report:
-            self.report[blurb[0]] = {}
-        self.report[blurb[0]][measure_to_add] = number
+        if blurb_id not in self.report:
+            self.report[blurb_id] = {}
+        self.report[blurb_id][measure_to_add] = number
 
     def add_general_statistics(self, measure_to_add: str):
         """Shortcut for adding to the first breakdown
@@ -46,7 +45,7 @@ def add_general_statistics(self, measure_to_add: str):
         Args:
             measure_to_add (str): _description_
         """
-        self.add(Blurbs.GeneralStatistics, measure_to_add)
+        self.add("GeneralStatistics", measure_to_add)
 
     def write_migration_report(
         self,
@@ -61,42 +60,54 @@ def write_migration_report(
             report_file (_type_):path to file
             time_started (datetime): The datetime stamp (in utc), of when the process started
         """
-        report_file.write(f"# {report_title}   \n")
         time_finished = datetime.now(timezone.utc)
-        report_file.write(f"{Blurbs.Introduction[1]}\n")
-        report_file.write("## Timings   \n")
-        report_file.write("   \n")
-        report_file.write("Measure | Value   \n")
-        report_file.write("--- | ---:   \n")
-        report_file.write(f"Time Started: | {datetime.isoformat(time_started)}   \n")
-        report_file.write(f"Time Finished: | {datetime.isoformat(time_finished)}   \n")
-        report_file.write(f"Elapsed time: | {time_finished-time_started}   \n")
+        report_file.write(
+            "\n".join(
+                [
+                    "# " + report_title,
+                    i18n.t("blurbs.Introduction.description"),
+                    "## " + i18n.t("Timings"),
+                    "",
+                    i18n.t("Measure") + " | " + i18n.t("Value"),
+                    "--- | ---:",
+                    i18n.t("Time Started:") + " | " + datetime.isoformat(time_started),
+                    i18n.t("Time Finished:") + " | " + datetime.isoformat(time_finished),
+                    i18n.t("Elapsed time:") + " | " + str(time_finished - time_started),
+                ]
+            )
+        )
         logging.info(f"Elapsed time: {time_finished-time_started}")
         for a in self.report:
-            blurb = self.report[a].get("blurb_tuple") or ("", "")
-            report_file.write("   \n")
-            report_file.write(f"## {blurb[0]}    \n")
-            report_file.write(f"{blurb[1]}    \n")
+            blurb_id = self.report[a].get("blurb_id") or ""
             report_file.write(
-                f"<details><summary>Click to expand all {len(self.report[a])} "
-                "things</summary>     \n"
+                "\n".join(
+                    [
+                        "",
+                        "## " + i18n.t(f"blurbs.{blurb_id}.title"),
+                        i18n.t(f"blurbs.{blurb_id}.description"),
+                        "<details><summary>"
+                        + i18n.t("Click to expand all %{count} things", count=len(self.report[a]))
+                        + "</summary>",
+                        "",
+                        i18n.t("Measure") + " | " + i18n.t("Count"),
+                        "--- | ---:",
+                    ]
+                    + [
+                        f"{k or 'EMPTY'} | {self.report[a][k]:,}"
+                        for k in sorted(self.report[a], key=as_str)
+                        if k != "blurb_id"
+                    ]
+                    + ["</details>", ""]
+                )
             )
-            report_file.write("   \n")
-            report_file.write("Measure | Count   \n")
-            report_file.write("--- | ---:   \n")
-            b = self.report[a]
-            sortedlist = [(k, b[k]) for k in sorted(b, key=as_str) if k != "blurb_tuple"]
-            for b in sortedlist:
-                report_file.write(f"{b[0] or 'EMPTY'} | {b[1]:,}   \n")
-            report_file.write("</details>   \n")
 
     def log_me(self):
         for a in self.report:
-            blurb = self.report[a].get("blurb_tuple") or ("", "")
-            logging.info(f"{blurb[0]}    ")
+            blurb_id = self.report[a].get("blurb_id") or ""
+            logging.info(f"{blurb_id}    ")
             logging.info("_______________")
             b = self.report[a]
-            sortedlist = [(k, b[k]) for k in sorted(b, key=as_str) if k != "blurb_tuple"]
+            sortedlist = [(k, b[k]) for k in sorted(b, key=as_str) if k != "blurb_id"]
             for b in sortedlist:
                 logging.info(f"{b[0] or 'EMPTY'} \t\t{b[1]:,}   ")
 
diff --git a/src/folio_migration_tools/migration_tasks/authority_transformer.py b/src/folio_migration_tools/migration_tasks/authority_transformer.py
index 551ab8c3..9b0717ac 100644
--- a/src/folio_migration_tools/migration_tasks/authority_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/authority_transformer.py
@@ -1,6 +1,7 @@
 import logging
 from typing import Annotated
 from typing import List
+import i18n
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
 from pydantic import Field
@@ -101,7 +102,7 @@ def wrap_up(self):
         self.processor.wrap_up()
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.mapper.migration_report.write_migration_report(
-                "Authority records transformation report",
+                i18n.t("Authority records transformation report"),
                 report_file,
                 self.start_datetime,
             )
diff --git a/src/folio_migration_tools/migration_tasks/batch_poster.py b/src/folio_migration_tools/migration_tasks/batch_poster.py
index f2daaca2..89b54b4a 100644
--- a/src/folio_migration_tools/migration_tasks/batch_poster.py
+++ b/src/folio_migration_tools/migration_tasks/batch_poster.py
@@ -4,6 +4,7 @@
 import sys
 import time
 import traceback
+import i18n
 from datetime import datetime
 from typing import Annotated
 from typing import List
@@ -19,7 +20,6 @@
 from folio_migration_tools.library_configuration import LibraryConfiguration
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 
 
@@ -177,7 +177,9 @@ def post_record_batch(self, batch, failed_recs_file, row):
             self.task_configuration.object_type in ["Instances", "Holdings", "Items"]
             and not self.task_configuration.use_safe_inventory_endpoints
         ):
-            self.migration_report.add_general_statistics("Set _version to -1 to enable upsert")
+            self.migration_report.add_general_statistics(
+                i18n.t("Set _version to -1 to enable upsert")
+            )
             json_rec["_version"] = -1
         if self.task_configuration.object_type == "SRS":
             json_rec["snapshotId"] = self.snapshot_id
@@ -249,7 +251,7 @@ def post_objects(self, url, body):
 
     def handle_generic_exception(self, exception, last_row, batch, num_records, failed_recs_file):
         logging.error("%s", exception)
-        self.migration_report.add(Blurbs.Details, "Generic exceptions (see log for details)")
+        self.migration_report.add("Details", i18n.t("Generic exceptions (see log for details)"))
         # logging.error("Failed row: %s", last_row)
         self.failed_batches += 1
         self.num_failures += len(batch)
@@ -262,7 +264,7 @@ def handle_generic_exception(self, exception, last_row, batch, num_records, fail
             sys.exit(1)
 
     def handle_unicode_error(self, unicode_error, last_row):
-        self.migration_report.add(Blurbs.Details, "Encoding errors")
+        self.migration_report.add("Details", i18n.t("Encoding errors"))
         logging.info("=========ERROR==============")
         logging.info(
             "%s Posting failed. Encoding error reading file",
@@ -313,7 +315,7 @@ def post_batch(self, batch, failed_recs_file, num_records, recursion_depth=0):
                         failed_user.get("externalSystemId", ""),
                         failed_user.get("errorMessage", ""),
                     )
-                    self.migration_report.add(Blurbs.Details, failed_user.get("errorMessage", ""))
+                    self.migration_report.add("Details", failed_user.get("errorMessage", ""))
             logging.info(
                 (
                     "Posting successful! Total rows: %s Total failed: %s "
@@ -416,15 +418,9 @@ def wrap_up(self):
             logging.info("Done posting %s records. %s failed", self.num_posted, self.num_failures)
 
         run = "second time" if self.performing_rerun else "first time"
-        self.migration_report.set(
-            Blurbs.GeneralStatistics, f"Records processed {run}", self.processed
-        )
-        self.migration_report.set(
-            Blurbs.GeneralStatistics, f"Records posted {run}", self.num_posted
-        )
-        self.migration_report.set(
-            Blurbs.GeneralStatistics, f"Failed to post {run}", self.num_failures
-        )
+        self.migration_report.set("GeneralStatistics", f"Records processed {run}", self.processed)
+        self.migration_report.set("GeneralStatistics", f"Records posted {run}", self.num_posted)
+        self.migration_report.set("GeneralStatistics", f"Failed to post {run}", self.num_failures)
         self.rerun_run()
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.migration_report.write_migration_report(
diff --git a/src/folio_migration_tools/migration_tasks/bibs_transformer.py b/src/folio_migration_tools/migration_tasks/bibs_transformer.py
index e490494c..51bd3149 100644
--- a/src/folio_migration_tools/migration_tasks/bibs_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/bibs_transformer.py
@@ -1,6 +1,7 @@
 import logging
 from typing import Annotated
 from typing import List
+import i18n
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
 from pydantic import Field
@@ -177,7 +178,7 @@ def wrap_up(self):
         self.processor.wrap_up()
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.mapper.migration_report.write_migration_report(
-                "Bibliographic records transformation report",
+                i18n.t("Bibliographic records transformation report"),
                 report_file,
                 self.start_datetime,
             )
diff --git a/src/folio_migration_tools/migration_tasks/courses_migrator.py b/src/folio_migration_tools/migration_tasks/courses_migrator.py
index 4a86d511..e554283d 100644
--- a/src/folio_migration_tools/migration_tasks/courses_migrator.py
+++ b/src/folio_migration_tools/migration_tasks/courses_migrator.py
@@ -4,6 +4,7 @@
 import sys
 import time
 import traceback
+import i18n
 from typing import Optional
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -19,7 +20,6 @@
     MappingFileMapperBase,
 )
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 
 
@@ -114,11 +114,11 @@ def do_work(self):
                 except Exception as excepion:
                     self.mapper.handle_generic_exception(idx, excepion)
                 self.mapper.migration_report.add(
-                    Blurbs.GeneralStatistics,
-                    f"Number of Legacy items in {full_path}",
+                    "GeneralStatistics",
+                    i18n.t("Number of Legacy items in %{container}", container=full_path),
                 )
                 self.mapper.migration_report.add_general_statistics(
-                    "Number of legacy items in total"
+                    i18n.t("Number of Legacy items in total")
                 )
                 self.print_progress(idx, start)
 
@@ -126,7 +126,7 @@ def wrap_up(self):
         self.extradata_writer.flush()
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.mapper.migration_report.write_migration_report(
-                "Courses migration report", report_file, self.mapper.start_datetime
+                i18n.t("Courses migration report"), report_file, self.mapper.start_datetime
             )
         self.clean_out_empty_logs()
 
diff --git a/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py b/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py
index 2af10d36..ddd8176e 100644
--- a/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py
@@ -5,6 +5,7 @@
 import sys
 import time
 import traceback
+import i18n
 from typing import Annotated
 from typing import List
 from typing import Optional
@@ -253,14 +254,14 @@ def wrap_up(self):
                         )
                     Helper.write_to_file(holdings_file, holding)
                     self.mapper.migration_report.add_general_statistics(
-                        "Holdings Records Written to disk"
+                        i18n.t("Holdings Records Written to disk")
                     )
             self.mapper.save_id_map_file(
                 self.folder_structure.holdings_id_map_path, self.holdings_id_map
             )
         with open(self.folder_structure.migration_reports_file, "w") as migration_report_file:
             self.mapper.migration_report.write_migration_report(
-                "Holdings transformation report",
+                i18n.t("Holdings transformation report"),
                 migration_report_file,
                 self.mapper.start_datetime,
             )
@@ -292,7 +293,9 @@ def validate_merge_criterias(self):
     def process_single_file(self, file_def: FileDefinition):
         full_path = self.folder_structure.data_folder / "items" / file_def.file_name
         with open(full_path, encoding="utf-8-sig") as records_file:
-            self.mapper.migration_report.add_general_statistics("Number of files processed")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("Number of files processed")
+            )
             start = time.time()
             records_processed = 0
             for idx, legacy_record in enumerate(self.mapper.get_objects(records_file, full_path)):
@@ -310,7 +313,7 @@ def process_single_file(self, file_def: FileDefinition):
                 except Exception as excepion:
                     self.mapper.handle_generic_exception(idx, excepion)
                 self.mapper.migration_report.add_general_statistics(
-                    "Number of Legacy items in file"
+                    i18n.t("Number of Legacy items in file")
                 )
                 if idx > 1 and idx % 10000 == 0:
                     elapsed = idx / (time.time() - start)
@@ -377,7 +380,7 @@ def merge_holding_in(
                 self.holdings[bw_key] = incoming_holding
                 self.mapper.create_and_write_boundwith_part(legacy_item_id, incoming_holding["id"])
                 self.mapper.migration_report.add_general_statistics(
-                    "Unique BW Holdings created from Items"
+                    i18n.t("Unique BW Holdings created from Items")
                 )
             else:
                 self.merge_holding(bw_key, incoming_holding)
@@ -388,7 +391,7 @@ def merge_holding_in(
                     legacy_item_id, self.holdings[bw_key], self.object_type
                 )
                 self.mapper.migration_report.add_general_statistics(
-                    "BW Items found tied to previously created BW Holdings"
+                    i18n.t("BW Items found tied to previously created BW Holdings")
                 )
         else:
             # Regular holding. Merge according to criteria
@@ -400,12 +403,12 @@ def merge_holding_in(
             )
             if self.holdings.get(new_holding_key, None):
                 self.mapper.migration_report.add_general_statistics(
-                    "Holdings already created from Item"
+                    i18n.t("Holdings already created from Item")
                 )
                 self.merge_holding(new_holding_key, incoming_holding)
             else:
                 self.mapper.migration_report.add_general_statistics(
-                    "Unique Holdings created from Items"
+                    i18n.t("Unique Holdings created from Items")
                 )
                 self.holdings[new_holding_key] = incoming_holding
 
diff --git a/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py b/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py
index 1b152a6c..cf8843a7 100644
--- a/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py
@@ -1,6 +1,7 @@
 '''Main "script."'''
 import csv
 import logging
+import i18n
 from typing import Annotated
 from typing import List
 
@@ -240,7 +241,7 @@ def wrap_up(self):
         self.processor.wrap_up()
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.mapper.migration_report.write_migration_report(
-                "Bibliographic records transformation report",
+                i18n.t("Bibliographic records transformation report"),
                 report_file,
                 self.start_datetime,
             )
diff --git a/src/folio_migration_tools/migration_tasks/items_transformer.py b/src/folio_migration_tools/migration_tasks/items_transformer.py
index cc8874ae..f29a45c4 100644
--- a/src/folio_migration_tools/migration_tasks/items_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/items_transformer.py
@@ -7,6 +7,7 @@
 import time
 import traceback
 import uuid
+import i18n
 from typing import Annotated
 from typing import List
 from typing import Optional
@@ -26,7 +27,6 @@
 )
 from folio_migration_tools.marc_rules_transformation.hrid_handler import HRIDHandler
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 
 csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
@@ -211,7 +211,7 @@ def do_work(self):
                     logging.exception(error_str, stack_info=True)
                     logging.fatal("Check source files for empty rows or missing reference data.")
                     self.mapper.migration_report.add(
-                        Blurbs.FailedFiles, f"{file_def.file_name} - {exception}"
+                        "FailedFiles", f"{file_def.file_name} - {exception}"
                     )
                     logging.fatal(error_str)
                     sys.exit(1)
@@ -224,7 +224,9 @@ def process_single_file(self, file_def: FileDefinition, results_file):
         logging.info("Processing %s", full_path)
         records_in_file = 0
         with open(full_path, encoding="utf-8-sig") as records_file:
-            self.mapper.migration_report.add_general_statistics("Number of files processed")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("Number of files processed")
+            )
             start = time.time()
             for idx, record in enumerate(self.mapper.get_objects(records_file, full_path)):
                 try:
@@ -258,7 +260,7 @@ def process_single_file(self, file_def: FileDefinition, results_file):
                     # TODO: turn this into a asynchrounous task
                     Helper.write_to_file(results_file, folio_rec)
                     self.mapper.migration_report.add_general_statistics(
-                        "Number of records written to disk"
+                        i18n.t("Number of records written to disk")
                     )
                     self.mapper.report_folio_mapping(folio_rec, self.mapper.schema)
                 except TransformationProcessError as process_error:
@@ -273,11 +275,11 @@ def process_single_file(self, file_def: FileDefinition, results_file):
                 except Exception as excepion:
                     self.mapper.handle_generic_exception(idx, excepion)
                 self.mapper.migration_report.add(
-                    Blurbs.GeneralStatistics,
-                    f"Number of Legacy items in {file_def}",
+                    "GeneralStatistics",
+                    i18n.t("Number of Legacy items in %{container}", container=file_def),
                 )
                 self.mapper.migration_report.add_general_statistics(
-                    "Number of legacy items in total"
+                    i18n.t("Number of Legacy items in total")
                 )
                 self.print_progress(idx, start)
                 records_in_file = idx + 1
@@ -333,7 +335,9 @@ def wrap_up(self):
         self.extradata_writer.flush()
         with open(self.folder_structure.migration_reports_file, "w") as migration_report_file:
             self.mapper.migration_report.write_migration_report(
-                "Item transformation report", migration_report_file, self.mapper.start_datetime
+                i18n.t("Item transformation report"),
+                migration_report_file,
+                self.mapper.start_datetime,
             )
             Helper.print_mapping_report(
                 migration_report_file,
diff --git a/src/folio_migration_tools/migration_tasks/loans_migrator.py b/src/folio_migration_tools/migration_tasks/loans_migrator.py
index 923a88bb..f87b298f 100644
--- a/src/folio_migration_tools/migration_tasks/loans_migrator.py
+++ b/src/folio_migration_tools/migration_tasks/loans_migrator.py
@@ -5,6 +5,7 @@
 import sys
 import time
 import traceback
+import i18n
 from datetime import datetime
 from datetime import timedelta
 from typing import Optional
@@ -25,7 +26,6 @@
 )
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 from folio_migration_tools.transaction_migration.legacy_loan import LegacyLoan
 from folio_migration_tools.transaction_migration.transaction_result import (
@@ -92,12 +92,12 @@ def __init__(
                 logging.info("Source data file contains %d rows", total_rows)
                 logging.info("Source data file contains %d empty rows", empty_rows)
                 self.migration_report.set(
-                    Blurbs.GeneralStatistics,
+                    "GeneralStatistics",
                     f"Total rows in {loans_file_path.name}",
                     total_rows,
                 )
                 self.migration_report.set(
-                    Blurbs.GeneralStatistics,
+                    "GeneralStatistics",
                     f"Empty rows in {loans_file_path.name}",
                     empty_rows,
                 )
@@ -183,7 +183,9 @@ def do_work(self):
                 self.valid_legacy_loans[starting_index:], start=1
             ):
                 t0_migration = time.time()
-                self.migration_report.add_general_statistics("Processed pre-validated loans")
+                self.migration_report.add_general_statistics(
+                    i18n.t("Processed pre-validated loans")
+                )
                 try:
                     self.checkout_single_loan(legacy_loan)
                 except Exception as ee:
@@ -203,44 +205,45 @@ def checkout_single_loan(self, legacy_loan: LegacyLoan):
         res_checkout = self.circulation_helper.check_out_by_barcode(legacy_loan)
 
         if res_checkout.was_successful:
-            self.migration_report.add(Blurbs.Details, "Checked out on first try")
-            self.migration_report.add_general_statistics("Successfully checked out")
+            self.migration_report.add("Details", i18n.t("Checked out on first try"))
+            self.migration_report.add_general_statistics(i18n.t("Successfully checked out"))
             self.set_renewal_count(legacy_loan, res_checkout)
             self.set_new_status(legacy_loan, res_checkout)
         elif res_checkout.should_be_retried:
             res_checkout2 = self.handle_checkout_failure(legacy_loan, res_checkout)
             if res_checkout2.was_successful and res_checkout2.folio_loan:
-                self.migration_report.add(Blurbs.Details, "Checked out on second try")
-                self.migration_report.add_general_statistics("Successfully checked out")
+                self.migration_report.add("Details", i18n.t("Checked out on second try"))
+                self.migration_report.add_general_statistics(i18n.t("Successfully checked out"))
                 logging.info("Checked out on second try")
                 self.set_renewal_count(legacy_loan, res_checkout2)
                 self.set_new_status(legacy_loan, res_checkout2)
             elif legacy_loan.item_barcode not in self.failed:
                 if res_checkout2.error_message == "Aged to lost and checked out":
                     self.migration_report.add(
-                        Blurbs.Details,
-                        f"Second failure: {res_checkout2.migration_report_message}",
+                        "Details",
+                        i18n.t("Second failure") + f": {res_checkout2.migration_report_message}",
                     )
                     logging.error(
                         f"{res_checkout2.error_message}. Item barcode: {legacy_loan.item_barcode}"
                     )
                 else:
                     self.failed[legacy_loan.item_barcode] = legacy_loan
-                    self.migration_report.add_general_statistics("Failed loans")
+                    self.migration_report.add_general_statistics(i18n.t("Failed loans"))
                     Helper.log_data_issue(
                         "", "Loans failing during checkout", json.dumps(legacy_loan.to_dict())
                     )
                     logging.error("Failed on second try: %s", res_checkout2.error_message)
                     self.migration_report.add(
-                        Blurbs.Details,
-                        f"Second failure: {res_checkout2.migration_report_message}",
+                        "Details",
+                        i18n.t("Second failure") + f": {res_checkout2.migration_report_message}",
                     )
         elif not res_checkout.should_be_retried:
             logging.error("Failed first time. No retries: %s", res_checkout.error_message)
-            self.migration_report.add_general_statistics("Failed loans")
+            self.migration_report.add_general_statistics(i18n.t("Failed loans"))
             self.migration_report.add(
-                Blurbs.Details,
-                f"Failed 1st time. No retries: {res_checkout.migration_report_message}",
+                "Details",
+                i18n.t("Failed 1st time. No retries")
+                + f": {res_checkout.migration_report_message}",
             )
             self.failed[legacy_loan.item_barcode] = legacy_loan
             Helper.log_data_issue(
@@ -265,7 +268,7 @@ def set_new_status(self, legacy_loan: LegacyLoan, res_checkout: TransactionResul
     def set_renewal_count(self, legacy_loan: LegacyLoan, res_checkout: TransactionResult):
         if legacy_loan.renewal_count > 0:
             self.update_open_loan(res_checkout.folio_loan, legacy_loan)
-            self.migration_report.add_general_statistics("Updated renewal count for loan")
+            self.migration_report.add_general_statistics(i18n.t("Updated renewal count for loan"))
 
     def wrap_up(self):
         for k, v in self.failed.items():
@@ -276,7 +279,7 @@ def wrap_up(self):
 
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.migration_report.write_migration_report(
-                "Loans migration report", report_file, self.start_datetime
+                i18n.t("Loans migration report"), report_file, self.start_datetime
             )
         self.clean_out_empty_logs()
 
@@ -309,17 +312,19 @@ def check_barcodes(self):
             has_patron_barcode = loan.patron_barcode in user_barcodes or not any(user_barcodes)
             if has_item_barcode and has_patron_barcode:
                 self.migration_report.add_general_statistics(
-                    "Loans verified against migrated user and item"
+                    i18n.t("Loans verified against migrated user and item")
                 )
                 yield loan
             else:
                 # Add this loan to failed loans for later correction and re-run.
                 self.failed[loan.item_barcode] = loan
-                self.migration_report.add_general_statistics("Failed loans")
+                self.migration_report.add_general_statistics(i18n.t("Failed loans"))
                 self.migration_report.add(
-                    Blurbs.DiscardedLoans,
-                    f"Loans discarded. Had migrated item barcode: {has_item_barcode}. "
-                    f"Had migrated user barcode: {has_patron_barcode}",
+                    "DiscardedLoans",
+                    i18n.t("Loans discarded. Had migrated item barcode")
+                    + f": {has_item_barcode}. "
+                    + i18n.t("Had migrated user barcode")
+                    + f": {has_patron_barcode}",
                 )
             if not has_item_barcode:
                 Helper.log_data_issue(
@@ -347,12 +352,12 @@ def load_and_validate_legacy_loans(self, loans_reader, service_point_id: str) ->
                 )
                 if any(legacy_loan.errors):
                     num_bad += 1
-                    self.migration_report.add_general_statistics("Loans failed pre-validation")
-                    self.migration_report.add_general_statistics("Failed loans")
+                    self.migration_report.add_general_statistics(
+                        i18n.t("Loans failed pre-validation")
+                    )
+                    self.migration_report.add_general_statistics(i18n.t("Failed loans"))
                     for error in legacy_loan.errors:
-                        self.migration_report.add(
-                            Blurbs.DiscardedLoans, f"{error[0]} - {error[1]}"
-                        )
+                        self.migration_report.add("DiscardedLoans", f"{error[0]} - {error[1]}")
                     # Add this loan to failed loans for later correction and re-run.
                     self.failed[
                         legacy_loan.item_barcode or f"no_barcode_{legacy_loan_count}"
@@ -407,16 +412,20 @@ def handle_checkout_failure(
             return self.checkout_to_inactice_user(legacy_loan)
         else:
             self.migration_report.add(
-                Blurbs.Details,
-                f"Other checkout failure: {folio_checkout.error_message}",
+                "Details",
+                i18n.t("Other checkout failure") + f": {folio_checkout.error_message}",
             )
             # First failure. Add to list of failed loans
             if legacy_loan.item_barcode not in self.failed:
                 self.failed[legacy_loan.item_barcode] = legacy_loan
             else:
                 logging.info(
-                    f"Loan already in failed. item barcode {legacy_loan.item_barcode} "
-                    f"Patron barcode: {legacy_loan.patron_barcode}"
+                    i18n.t("Loan already in failed.")
+                    + " "
+                    + i18n.t("item barcode")
+                    + f": {legacy_loan.item_barcode}"
+                    + i18n.t("Patron barcode")
+                    + f": {legacy_loan.patron_barcode}",
                 )
                 self.failed_and_not_dupe[legacy_loan.item_barcode] = [
                     legacy_loan,
@@ -426,7 +435,7 @@ def handle_checkout_failure(
                     f"Duplicate loans (or failed twice) Item barcode: "
                     f"{legacy_loan.item_barcode} Patron barcode: {legacy_loan.patron_barcode}"
                 )
-                self.migration_report.add(Blurbs.Details, "Duplicate loans (or failed twice)")
+                self.migration_report.add("Details", i18n.t("Duplicate loans (or failed twice)"))
                 del self.failed[legacy_loan.item_barcode]
             return TransactionResult(False, False, "", "", "")
 
@@ -438,10 +447,10 @@ def checkout_to_inactice_user(self, legacy_loan) -> TransactionResult:
         self.activate_user(user)
         logging.debug("Successfully Activated user")
         res = self.circulation_helper.check_out_by_barcode(legacy_loan)  # checkout_and_update
-        self.migration_report.add(Blurbs.Details, res.migration_report_message)
+        self.migration_report.add("Details", res.migration_report_message)
         self.deactivate_user(user, expiration_date)
         logging.debug("Successfully Deactivated user again")
-        self.migration_report.add(Blurbs.Details, "Handled inactive users")
+        self.migration_report.add("Details", i18n.t("Handled inactive users"))
         return res
 
     def handle_aged_to_lost_item(self, legacy_loan: LegacyLoan) -> TransactionResult:
@@ -450,8 +459,8 @@ def handle_aged_to_lost_item(self, legacy_loan: LegacyLoan) -> TransactionResult
                 False,
                 False,
                 legacy_loan,
-                "Aged to lost and checked out",
-                "Aged to lost and checked out",
+                i18n.t("Aged to lost and checked out"),
+                i18n.t("Aged to lost and checked out"),
             )
 
         else:
@@ -463,7 +472,7 @@ def handle_aged_to_lost_item(self, legacy_loan: LegacyLoan) -> TransactionResult
             self.set_item_status(legacy_loan)
             s = "Successfully Checked out Aged to lost item and put the status back"
             logging.info(s)
-            self.migration_report.add(Blurbs.Details, s)
+            self.migration_report.add("Details", s)
             return res_checkout
 
     def update_open_loan(self, folio_loan: dict, legacy_loan: LegacyLoan):
@@ -485,19 +494,19 @@ def update_open_loan(self, folio_loan: dict, legacy_loan: LegacyLoan):
             if req.status_code == 422:
                 error_message = json.loads(req.text)["errors"][0]["message"]
                 s = f"Update open loan error: {error_message} {req.status_code}"
-                self.migration_report.add(Blurbs.Details, s)
+                self.migration_report.add("Details", s)
                 logging.error(s)
                 return False
             elif req.status_code in [201, 204]:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Successfully updated open loan ({req.status_code})",
+                    "Details",
+                    i18n.t("Successfully updated open loan") + f" ({req.status_code})",
                 )
                 return True
             else:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Update open loan error http status: {req.status_code}",
+                    "Details",
+                    i18n.t("Update open loan error http status") + f": {req.status_code}",
                 )
                 req.raise_for_status()
             logging.debug("Updating open loan was successful")
@@ -527,11 +536,11 @@ def declare_lost(self, folio_loan):
             "servicePointId": str(self.task_configuration.fallback_service_point_id),
         }
         logging.debug(f"Declare lost data: {json.dumps(data, indent=4)}")
-        if self.folio_put_post(declare_lost_url, data, "POST", "Declare item as lost"):
-            self.migration_report.add(Blurbs.Details, "Successfully declared loan as lost")
+        if self.folio_put_post(declare_lost_url, data, "POST", i18n.t("Declare item as lost")):
+            self.migration_report.add("Details", i18n.t("Successfully declared loan as lost"))
         else:
             logging.error(f"Unsuccessfully declared loan {folio_loan} as lost")
-            self.migration_report.add(Blurbs.Details, "Unsuccessfully declared loan as lost")
+            self.migration_report.add("Details", i18n.t("Unsuccessfully declared loan as lost"))
 
     def claim_returned(self, folio_loan):
         claim_returned_url = f"/circulation/loans/{folio_loan['id']}/claim-item-returned"
@@ -542,15 +551,17 @@ def claim_returned(self, folio_loan):
             "comment": "Created at migration. Date is due date + 1 day",
         }
         logging.debug(f"Claim returned data:\t{json.dumps(data)}")
-        if self.folio_put_post(claim_returned_url, data, "POST", "Declare item as lost"):
+        if self.folio_put_post(claim_returned_url, data, "POST", i18n.t("Declare item as lost")):
             self.migration_report.add(
-                Blurbs.Details, "Successfully declared loan as Claimed returned"
+                "Details", i18n.t("Successfully declared loan as Claimed returned")
             )
         else:
             logging.error(f"Unsuccessfully declared loan {folio_loan} as Claimed returned")
             self.migration_report.add(
-                Blurbs.Details,
-                f"Unsuccessfully declared loan {folio_loan} as Claimed returned",
+                "Details",
+                i18n.t(
+                    "Unsuccessfully declared loan %{loan} as Claimed returned", loan=folio_loan
+                ),
             )
 
     def set_item_status(self, legacy_loan: LegacyLoan):
@@ -565,8 +576,11 @@ def set_item_status(self, legacy_loan: LegacyLoan):
             folio_item["status"]["name"] = legacy_loan.next_item_status
             if self.update_item(folio_item):
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Successfully set item status to {legacy_loan.next_item_status}",
+                    "Details",
+                    i18n.t(
+                        "Successfully set item status to %{status}",
+                        status=legacy_loan.next_item_status,
+                    ),
                 )
                 logging.debug(
                     f"Successfully set item with barcode "
@@ -580,8 +594,11 @@ def set_item_status(self, legacy_loan: LegacyLoan):
                     f"{legacy_loan.item_barcode} to {legacy_loan.next_item_status}"
                 )
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Error setting item status to {legacy_loan.next_item_status}",
+                    "Details",
+                    i18n.t(
+                        "Error setting item status to %{status}",
+                        status=legacy_loan.next_item_status,
+                    ),
                 )
         except Exception as ee:
             logging.error(
@@ -593,21 +610,21 @@ def set_item_status(self, legacy_loan: LegacyLoan):
     def activate_user(self, user):
         user["active"] = True
         self.update_user(user)
-        self.migration_report.add(Blurbs.Details, "Successfully activated user")
+        self.migration_report.add("Details", i18n.t("Successfully activated user"))
 
     def deactivate_user(self, user, expiration_date):
         user["expirationDate"] = expiration_date
         user["active"] = False
         self.update_user(user)
-        self.migration_report.add(Blurbs.Details, "Successfully deactivated user")
+        self.migration_report.add("Details", i18n.t("Successfully deactivated user"))
 
     def update_item(self, item):
         url = f'/item-storage/items/{item["id"]}'
-        return self.folio_put_post(url, item, "PUT", "Update item")
+        return self.folio_put_post(url, item, "PUT", i18n.t("Update item"))
 
     def update_user(self, user):
         url = f'/users/{user["id"]}'
-        self.folio_put_post(url, user, "PUT", "Update user")
+        self.folio_put_post(url, user, "PUT", i18n.t("Update user"))
 
     def get_user_by_barcode(self, barcode):
         url = f'{self.folio_client.okapi_url}/users?query=(barcode=="{barcode}")'
@@ -637,18 +654,28 @@ def folio_put_post(self, url, data_dict, verb, action_description=""):
                 error_message = json.loads(resp.text)["errors"][0]["message"]
                 logging.error(error_message)
                 self.migration_report.add(
-                    Blurbs.Details, f"{action_description} error: {error_message}"
+                    "Details",
+                    i18n.t(
+                        "%{action} error: %{message}",
+                        action=action_description,
+                        message=error_message,
+                    ),
                 )
                 resp.raise_for_status()
             elif resp.status_code in [201, 204]:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Successfully {action_description} ({resp.status_code})",
+                    "Details",
+                    i18n.t("Successfully %{action}", action=action_description)
+                    + f" ({resp.status_code})",
                 )
             else:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"{action_description} error. http status: {resp.status_code}",
+                    "Details",
+                    i18n.t(
+                        "%{action} error. http status: %{status}",
+                        action=action_description,
+                        status=resp.status_code,
+                    ),
                 )
 
                 resp.raise_for_status()
@@ -670,27 +697,28 @@ def change_due_date(self, folio_loan, legacy_loan):
             if req.status_code == 422:
                 error_message = json.loads(req.text)["errors"][0]["message"]
                 self.migration_report.add(
-                    Blurbs.Details, f"Change due date error: {error_message}"
+                    "Details", i18n.t("Change due date error") + f": {error_message}"
                 )
                 logging.info(
                     f"{error_message}\t",
                 )
-                self.migration_report.add(Blurbs.Details, error_message)
+                self.migration_report.add("Details", error_message)
                 return False
             elif req.status_code == 201:
                 self.migration_report.add(
-                    Blurbs.Details, f"Successfully changed due date ({req.status_code})"
+                    "Details", i18n.t("Successfully changed due date") + f" ({req.status_code})"
                 )
                 return True, json.loads(req.text), None
             elif req.status_code == 204:
                 self.migration_report.add(
-                    Blurbs.Details, f"Successfully changed due date ({req.status_code})"
+                    "Details", i18n.t("Successfully changed due date") + f" ({req.status_code})"
                 )
                 return True, None, None
             else:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Update open loan error http status: {req.status_code}",
+                    "Details",
+                    i18n.t("Update open loan error http status"),
+                    f": {req.status_code}",
                 )
                 req.raise_for_status()
         except HTTPError as exception:
diff --git a/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py b/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py
index 51aec086..d144ddb0 100644
--- a/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py
@@ -4,6 +4,7 @@
 import sys
 import time
 import traceback
+import i18n
 from typing import List
 from typing import Optional
 
@@ -22,7 +23,6 @@
     MappingFileMapperBase,
 )
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 
 
@@ -107,13 +107,15 @@ def do_work(self):
                     "Check source files for empty rows or missing reference data"
                 )
                 logging.exception(error_str)
-                self.mapper.migration_report.add(Blurbs.FailedFiles, f"{file} - {ee}")
+                self.mapper.migration_report.add("FailedFiles", f"{file} - {ee}")
                 sys.exit()
 
     def process_single_file(self, file_def: FileDefinition):
         full_path = self.folder_structure.legacy_records_folder / file_def.file_name
         with open(full_path, encoding="utf-8-sig") as records_file:
-            self.mapper.migration_report.add_general_statistics("Number of files processed")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("Number of files processed")
+            )
             start = time.time()
 
             for idx, record in enumerate(self.mapper.get_objects(records_file, full_path)):
@@ -166,7 +168,9 @@ def wrap_up(self):
                 self.folder_structure.migration_reports_file,
             )
             self.mapper.migration_report.write_migration_report(
-                "Manual fee/fine transformation report", migration_report_file, self.start_datetime
+                i18n.t("Manual fee/fine transformation report"),
+                migration_report_file,
+                self.start_datetime,
             )
 
             Helper.print_mapping_report(
diff --git a/src/folio_migration_tools/migration_tasks/orders_transformer.py b/src/folio_migration_tools/migration_tasks/orders_transformer.py
index 5e0d83be..892f10c4 100644
--- a/src/folio_migration_tools/migration_tasks/orders_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/orders_transformer.py
@@ -4,6 +4,7 @@
 import logging
 import sys
 import time
+import i18n
 from os.path import isfile
 from typing import List
 from typing import Optional
@@ -24,7 +25,6 @@
     CompositeOrderMapper,
 )
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 
 csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
 
@@ -151,7 +151,9 @@ def process_single_file(self, filename):
         with open(filename, encoding="utf-8-sig") as records_file, open(
             self.folder_structure.created_objects_path, "w+"
         ) as results_file:
-            self.mapper.migration_report.add_general_statistics("Number of files processed")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("Number of files processed")
+            )
             start = time.time()
             records_processed = 0
             for idx, record in enumerate(self.mapper.get_objects(records_file, filename)):
@@ -169,7 +171,7 @@ def process_single_file(self, filename):
                     self.mapper.perform_additional_mapping(legacy_id, folio_rec)
 
                     self.mapper.migration_report.add_general_statistics(
-                        "TOTAL Purchase Order Lines created"
+                        i18n.t("TOTAL Purchase Order Lines created")
                     )
                     self.mapper.report_folio_mapping(folio_rec, self.mapper.composite_order_schema)
                     self.mapper.notes_mapper.map_notes(
@@ -204,7 +206,9 @@ def process_single_file(self, filename):
             )
             logging.info("Storing last record to disk")
             Helper.write_to_file(results_file, self.current_folio_record)
-            self.mapper.migration_report.add_general_statistics("TOTAL Purchase Orders created")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("TOTAL Purchase Orders created")
+            )
 
     def do_work(self):
         logging.info("Getting started!")
@@ -219,7 +223,7 @@ def do_work(self):
                     "Check source files for empty lines or missing reference data"
                 )
                 logging.exception(error_str)
-                self.mapper.migration_report.add(Blurbs.FailedFiles, f"{file} - {ee}")
+                self.mapper.migration_report.add("FailedFiles", f"{file} - {ee}")
                 sys.exit()
 
     def wrap_up(self):
@@ -230,7 +234,7 @@ def wrap_up(self):
                 self.folder_structure.migration_reports_file,
             )
             self.mapper.migration_report.write_migration_report(
-                "Pruchase Orders and Purchase Order Lines Transformation Report",
+                i18n.t("Pruchase Orders and Purchase Order Lines Transformation Report"),
                 migration_report_file,
                 self.start_datetime,
             )
@@ -250,7 +254,9 @@ def merge_into_orders_with_embedded_pols(self, folio_rec, results_file):
         if folio_rec["id"] != self.current_folio_record["id"]:
             # Writes record to file
             Helper.write_to_file(results_file, self.current_folio_record)
-            self.mapper.migration_report.add_general_statistics("TOTAL Purchase Orders created")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("TOTAL Purchase Orders created")
+            )
             self.current_folio_record = folio_rec
 
         else:
@@ -261,7 +267,7 @@ def merge_into_orders_with_embedded_pols(self, folio_rec, results_file):
                     folio_rec.get("compositePoLines", [])
                 )
                 self.mapper.migration_report.add_general_statistics(
-                    "Rows merged to create Purchase Orders"
+                    i18n.t("Rows merged to create Purchase Orders")
                 )
             for key in diff.affected_paths:
-                self.mapper.migration_report.add(Blurbs.DiffsBetweenOrders, key)
+                self.mapper.migration_report.add("DiffsBetweenOrders", key)
diff --git a/src/folio_migration_tools/migration_tasks/organization_transformer.py b/src/folio_migration_tools/migration_tasks/organization_transformer.py
index 3ebdc372..1eec48a6 100644
--- a/src/folio_migration_tools/migration_tasks/organization_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/organization_transformer.py
@@ -5,6 +5,7 @@
 import sys
 import time
 import uuid
+import i18n
 from hashlib import sha1
 from os.path import isfile
 from typing import List
@@ -24,7 +25,6 @@
     OrganizationMapper,
 )
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 
 csv.field_size_limit(int(ctypes.c_ulong(-1).value // 2))
@@ -134,7 +134,9 @@ def process_single_file(self, filename):
         with open(filename, encoding="utf-8-sig") as records_file, open(
             self.folder_structure.created_objects_path, "w+"
         ) as results_file:
-            self.mapper.migration_report.add_general_statistics("Number of files processed")
+            self.mapper.migration_report.add_general_statistics(
+                i18n.t("Number of files processed")
+            )
             start = time.time()
             records_processed = 0
             for idx, record in enumerate(self.mapper.get_objects(records_file, filename)):
@@ -176,10 +178,10 @@ def process_single_file(self, filename):
                     self.mapper.handle_generic_exception(idx, excepion)
 
                 self.mapper.migration_report.add_general_statistics(
-                    "Number of objects in source data file"
+                    i18n.t("Number of objects in source data file")
                 )
                 self.mapper.migration_report.add_general_statistics(
-                    "Number of organizations created"
+                    i18n.t("Number of organizations created")
                 )
 
                 # TODO Rewrite to base % value on number of rows in file
@@ -209,7 +211,7 @@ def do_work(self):
                     "Check source files for empty rows or missing reference data"
                 )
                 logging.exception(error_str)
-                self.mapper.migration_report.add(Blurbs.FailedFiles, f"{file} - {ee}")
+                self.mapper.migration_report.add("FailedFiles", f"{file} - {ee}")
                 sys.exit()
 
     def wrap_up(self):
@@ -221,7 +223,9 @@ def wrap_up(self):
                 self.folder_structure.migration_reports_file,
             )
             self.mapper.migration_report.write_migration_report(
-                "Ogranization transformation report", migration_report_file, self.start_datetime
+                i18n.t("Ogranization transformation report"),
+                migration_report_file,
+                self.start_datetime,
             )
 
             Helper.print_mapping_report(
@@ -279,8 +283,8 @@ def validate_uri(self, record):
                 valid_interfaces.append(interface)
             else:
                 self.mapper.migration_report.add(
-                    Blurbs.MalformedInterfaceUri,
-                    "Interfaces",
+                    "MalformedInterfaceUri",
+                    i18n.t("Interfaces"),
                 )
                 Helper.log_data_issue(
                     f"{record['code']}",
@@ -356,7 +360,7 @@ def create_referenced_extradata_object(self, embedded_object, extradata_object_t
 
         if len(identical_objects) > 0:
             self.mapper.migration_report.add_general_statistics(
-                f"Number of reoccuring identical {extradata_object_type}"
+                i18n.t("Number of reoccuring identical %{type}", type=extradata_object_type)
             )
             Helper.log_data_issue(
                 f"{self.legacy_id}",
@@ -371,7 +375,7 @@ def create_referenced_extradata_object(self, embedded_object, extradata_object_t
         self.embedded_extradata_object_cache.add(embedded_object_hash)
 
         self.mapper.migration_report.add_general_statistics(
-            f"Number of linked {extradata_object_type} created"
+            i18n.t("Number of linked %{type} created", type=extradata_object_type)
         )
 
         return extradata_object_uuid
diff --git a/src/folio_migration_tools/migration_tasks/requests_migrator.py b/src/folio_migration_tools/migration_tasks/requests_migrator.py
index 18e6d3fe..6ab65e73 100644
--- a/src/folio_migration_tools/migration_tasks/requests_migrator.py
+++ b/src/folio_migration_tools/migration_tasks/requests_migrator.py
@@ -3,6 +3,7 @@
 import logging
 import sys
 import time
+import i18n
 from typing import Optional
 from zoneinfo import ZoneInfo
 
@@ -15,7 +16,6 @@
 from folio_migration_tools.library_configuration import LibraryConfiguration
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 from folio_migration_tools.transaction_migration.legacy_request import LegacyRequest
 
@@ -99,7 +99,7 @@ def __init__(
 
     def prepare_legacy_request(self, legacy_request: LegacyRequest):
         patron = self.circulation_helper.get_user_by_barcode(legacy_request.patron_barcode)
-        self.migration_report.add_general_statistics("Patron lookups performed")
+        self.migration_report.add_general_statistics(i18n.t("Patron lookups performed"))
 
         if not patron:
             logging.error(f"No user with barcode {legacy_request.patron_barcode} found in FOLIO")
@@ -108,16 +108,20 @@ def prepare_legacy_request(self, legacy_request: LegacyRequest):
                 "No user with barcode.",
                 f"{legacy_request.patron_barcode}",
             )
-            self.migration_report.add_general_statistics("No user with barcode found in FOLIO")
+            self.migration_report.add_general_statistics(
+                i18n.t("No user with barcode found in FOLIO")
+            )
             self.failed_requests.add(legacy_request)
             return False, legacy_request
         legacy_request.patron_id = patron.get("id")
 
         item = self.circulation_helper.get_item_by_barcode(legacy_request.item_barcode)
-        self.migration_report.add_general_statistics("Item lookups performed")
+        self.migration_report.add_general_statistics(i18n.t("Item lookups performed"))
         if not item:
             logging.error(f"No item with barcode {legacy_request.item_barcode} found in FOLIO")
-            self.migration_report.add_general_statistics("No item with barcode found in FOLIO")
+            self.migration_report.add_general_statistics(
+                i18n.t("No item with barcode found in FOLIO")
+            )
             Helper.log_data_issue(
                 f"{legacy_request.item_barcode}",
                 "No item with barcode",
@@ -126,14 +130,16 @@ def prepare_legacy_request(self, legacy_request: LegacyRequest):
             self.failed_requests.add(legacy_request)
             return False, legacy_request
         holding = self.circulation_helper.get_holding_by_uuid(item.get("holdingsRecordId"))
-        self.migration_report.add_general_statistics("Holdings lookups performed")
+        self.migration_report.add_general_statistics(i18n.t("Holdings lookups performed"))
         legacy_request.item_id = item.get("id")
         legacy_request.holdings_record_id = item.get("holdingsRecordId")
         legacy_request.instance_id = holding.get("instanceId")
         if item["status"]["name"] in ["Available"]:
             legacy_request.request_type = "Page"
             logging.info(f'Setting request to Page, since the status is {item["status"]["name"]}')
-        self.migration_report.add_general_statistics("Valid, prepared requests, ready for posting")
+        self.migration_report.add_general_statistics(
+            i18n.t("Valid, prepared requests, ready for posting")
+        )
         return True, legacy_request
 
     def do_work(self):
@@ -152,11 +158,11 @@ def do_work(self):
                         self.folio_client, legacy_request, self.migration_report
                     ):
                         self.migration_report.add_general_statistics(
-                            "Successfully migrated requests"
+                            i18n.t("Successfully migrated requests")
                         )
                     else:
                         self.migration_report.add_general_statistics(
-                            "Unsuccessfully migrated requests"
+                            i18n.t("Unsuccessfully migrated requests")
                         )
                         self.failed_requests.add(legacy_request)
                 if num_requests == 1:
@@ -179,7 +185,7 @@ def wrap_up(self):
 
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.migration_report.write_migration_report(
-                "Requests migration report", report_file, self.start_datetime
+                i18n.t("Requests migration report"), report_file, self.start_datetime
             )
         self.clean_out_empty_logs()
 
@@ -216,17 +222,20 @@ def check_barcodes(self):
             has_patron_barcode = request.patron_barcode in user_barcodes
             if has_item_barcode and has_patron_barcode:
                 self.migration_report.add_general_statistics(
-                    "Requests successfully verified against migrated users and items"
+                    i18n.t("Requests successfully verified against migrated users and items")
                 )
                 yield request
             else:
                 self.migration_report.add(
-                    Blurbs.DiscardedLoans,
-                    f"Requests discarded. Had migrated item barcode: {has_item_barcode}. "
-                    f"Had migrated user barcode: {has_patron_barcode}",
+                    "DiscardedLoans",
+                    i18n.t(
+                        "Requests discarded. Had migrated item barcode: %{item_barcode}.\n Had migrated user barcode: %{patron_barcode}",
+                        item_barcode=has_item_barcode,
+                        patron_barcode=has_patron_barcode,
+                    ),
                 )
                 self.migration_report.add_general_statistics(
-                    "Requests that failed verification against migrated users and items"
+                    i18n.t("Requests that failed verification against migrated users and items")
                 )
             if not has_item_barcode:
                 Helper.log_data_issue(
@@ -245,7 +254,7 @@ def load_and_validate_legacy_requests(self, requests_reader):
         num_bad = 0
         logging.info("Validating legacy requests in file...")
         for legacy_reques_count, legacy_request_dict in enumerate(requests_reader, start=1):
-            self.migration_report.add_general_statistics("Requests in file")
+            self.migration_report.add_general_statistics(i18n.t("Requests in file"))
             try:
                 legacy_request = LegacyRequest(
                     legacy_request_dict,
@@ -254,18 +263,20 @@ def load_and_validate_legacy_requests(self, requests_reader):
                 )
                 if any(legacy_request.errors):
                     num_bad += 1
-                    self.migration_report.add_general_statistics("Requests with valueErrors")
+                    self.migration_report.add_general_statistics(
+                        i18n.t("Requests with valueErrors")
+                    )
                     for error in legacy_request.errors:
-                        self.migration_report.add(
-                            Blurbs.DiscardedRequests, f"{error[0]} - {error[1]}"
-                        )
+                        self.migration_report.add("DiscardedRequests", f"{error[0]} - {error[1]}")
                         Helper.log_data_issue(
                             legacy_request.item_barcode,
                             f"{error[0]} - {error[1]}",
                             json.dumps(legacy_request.to_source_dict()),
                         )
                 else:
-                    self.migration_report.add_general_statistics("Requests with valid source data")
+                    self.migration_report.add_general_statistics(
+                        i18n.t("Requests with valid source data")
+                    )
                     yield legacy_request
             except ValueError as ve:
                 logging.exception(ve)
diff --git a/src/folio_migration_tools/migration_tasks/reserves_migrator.py b/src/folio_migration_tools/migration_tasks/reserves_migrator.py
index 353a0d22..54dc0f32 100644
--- a/src/folio_migration_tools/migration_tasks/reserves_migrator.py
+++ b/src/folio_migration_tools/migration_tasks/reserves_migrator.py
@@ -4,6 +4,7 @@
 import sys
 import time
 import traceback
+import i18n
 from typing import Dict
 from urllib.error import HTTPError
 
@@ -16,7 +17,6 @@
 from folio_migration_tools.library_configuration import LibraryConfiguration
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.migration_task_base import MigrationTaskBase
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.task_configuration import AbstractTaskConfiguration
 from folio_migration_tools.transaction_migration.legacy_reserve import LegacyReserve
 
@@ -65,7 +65,7 @@ def do_work(self):
         logging.info("Starting")
         for num_reserves, legacy_reserve in enumerate(self.valid_reserves, start=1):
             t0_migration = time.time()
-            self.migration_report.add_general_statistics("Processed reserves")
+            self.migration_report.add_general_statistics(i18n.t("Processed reserves"))
             try:
                 self.post_single_reserve(legacy_reserve)
             except Exception as ee:
@@ -78,10 +78,14 @@ def do_work(self):
     def post_single_reserve(self, legacy_reserve: LegacyReserve):
         try:
             path = f"/coursereserves/courselistings/{legacy_reserve.course_listing_id}/reserves"
-            if self.folio_put_post(path, legacy_reserve.to_dict(), "POST", "Posted reserves"):
-                self.migration_report.add_general_statistics("Successfully posted reserves")
+            if self.folio_put_post(
+                path, legacy_reserve.to_dict(), "POST", i18n.t("Posted reserves")
+            ):
+                self.migration_report.add_general_statistics(
+                    i18n.t("Successfully posted reserves")
+                )
             else:
-                self.migration_report.add_general_statistics("Failure to post reserve")
+                self.migration_report.add_general_statistics(i18n.t("Failure to post reserve"))
         except Exception as ee:
             logging.error(ee)
 
@@ -89,12 +93,12 @@ def wrap_up(self):
         self.extradata_writer.flush()
         for k, v in self.failed.items():
             self.failed_and_not_dupe[k] = [v.to_dict()]
-        self.migration_report.set(Blurbs.GeneralStatistics, "Failed loans", len(self.failed))
+        self.migration_report.set("GeneralStatistics", "Failed loans", len(self.failed))
         self.write_failed_reserves_to_file()
 
         with open(self.folder_structure.migration_reports_file, "w+") as report_file:
             self.migration_report.write_migration_report(
-                "Reserves migration report", report_file, self.start_datetime
+                i18n.t("Reserves migration report"), report_file, self.start_datetime
             )
         self.clean_out_empty_logs()
 
@@ -123,12 +127,12 @@ def check_barcodes(self):
             has_item_barcode = loan.item_barcode in item_barcodes or not any(item_barcodes)
             if has_item_barcode:
                 self.migration_report.add_general_statistics(
-                    "Reserve verified against migrated item"
+                    i18n.t("Reserve verified against migrated item")
                 )
                 yield loan
             else:
                 self.migration_report.add(
-                    Blurbs.DiscardedLoans, "Reserve discarded. Could not find migrated barcode"
+                    "DiscardedLoans", i18n.t("Reserve discarded. Could not find migrated barcode")
                 )
 
     def load_and_validate_legacy_reserves(self, reserves_reader):
@@ -143,11 +147,9 @@ def load_and_validate_legacy_reserves(self, reserves_reader):
                 )
                 if any(legacy_reserve.errors):
                     num_bad += 1
-                    self.migration_report.add_general_statistics("Discarded reserves")
+                    self.migration_report.add_general_statistics(i18n.t("Discarded reserves"))
                     for error in legacy_reserve.errors:
-                        self.migration_report.add(
-                            Blurbs.DiscardedReserves, f"{error[0]} - {error[1]}"
-                        )
+                        self.migration_report.add("DiscardedReserves", f"{error[0]} - {error[1]}")
                 else:
                     yield legacy_reserve
             except ValueError as ve:
@@ -183,18 +185,28 @@ def folio_put_post(self, url, data_dict, verb, action_description=""):
                 error_message = json.loads(resp.text)["errors"][0]["message"]
                 logging.error(error_message)
                 self.migration_report.add(
-                    Blurbs.Details, f"{action_description} error: {error_message}"
+                    "Details",
+                    i18n.t(
+                        "%{action} error: %{message}",
+                        action=action_description,
+                        message=error_message,
+                    ),
                 )
                 resp.raise_for_status()
             elif resp.status_code in [201, 204]:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"Successfully {action_description} ({resp.status_code})",
+                    "Details",
+                    i18n.t("Successfully %{action}", action=action_description)
+                    + f" ({resp.status_code})",
                 )
             else:
                 self.migration_report.add(
-                    Blurbs.Details,
-                    f"{action_description} error. http status: {resp.status_code}",
+                    "Details",
+                    i18n.t(
+                        "%{action} error. http status: %{status}",
+                        action=action_description,
+                        status=resp.status_code,
+                    ),
                 )
                 logging.error(json.dumps(data_dict))
                 resp.raise_for_status()
diff --git a/src/folio_migration_tools/migration_tasks/user_transformer.py b/src/folio_migration_tools/migration_tasks/user_transformer.py
index 9640ec13..419a3d8e 100644
--- a/src/folio_migration_tools/migration_tasks/user_transformer.py
+++ b/src/folio_migration_tools/migration_tasks/user_transformer.py
@@ -1,6 +1,7 @@
 import json
 import logging
 import sys
+import i18n
 from typing import Optional
 
 from folio_uuid.folio_namespaces import FOLIONamespaces
@@ -133,12 +134,14 @@ def do_work(self):
                                 logging.info("## First FOLIO  user")
                                 logging.info(json.dumps(folio_user, indent=4, sort_keys=True))
                             self.mapper.migration_report.add_general_statistics(
-                                "Successful user transformations"
+                                i18n.t("Successful user transformations")
                             )
                             if num_users % 1000 == 0:
                                 logging.info(f"{num_users} users processed.")
                         except TransformationRecordFailedError as tre:
-                            self.mapper.migration_report.add_general_statistics("Records failed")
+                            self.mapper.migration_report.add_general_statistics(
+                                i18n.t("Records failed")
+                            )
                             Helper.log_data_issue(tre.index_or_id, tre.message, tre.data_value)
                             logging.error(tre)
                         except TransformationProcessError as tpe:
@@ -154,7 +157,7 @@ def do_work(self):
                             logging.error(num_users)
                             logging.error(json.dumps(legacy_user))
                             self.mapper.migration_report.add_general_statistics(
-                                "Failed user transformations"
+                                i18n.t("Failed user transformations")
                             )
                             logging.error(ee, exc_info=True)
 
@@ -168,7 +171,9 @@ def wrap_up(self):
         self.extradata_writer.flush()
         with open(self.folder_structure.migration_reports_file, "w") as migration_report_file:
             self.mapper.migration_report.write_migration_report(
-                "Users transformation report", migration_report_file, self.mapper.start_datetime
+                i18n.t("Users transformation report"),
+                migration_report_file,
+                self.mapper.start_datetime,
             )
             Helper.print_mapping_report(
                 migration_report_file,
diff --git a/src/folio_migration_tools/report_blurbs.py b/src/folio_migration_tools/report_blurbs.py
deleted file mode 100644
index ed002870..00000000
--- a/src/folio_migration_tools/report_blurbs.py
+++ /dev/null
@@ -1,284 +0,0 @@
-from enum import Enum
-
-
-class ReportSection(Enum):
-    GENERAL = "General statistics"
-    LIBRARY_ACTION = "Library action needed"
-    ADDITIONAL_INFORMATION = "Additional information"
-    TECHNICAL = "Technical information"
-
-
-class Blurbs:
-    DiffsBetweenOrders = (
-        "Differences between generated orders with same Legacy Identifier",
-        "This is a technical report that helps you to identify differences in the mapped order fields. ",
-    )
-    AuthoritySources = ("Authorization sources and related information", "")
-    IncompleteSubPropertyRemoved = (
-        "Sub-property removed due to missing required fields",
-        "Add the missing required information to the record in your current ILS to ensure that it can be migrated over.",
-    )
-    POLCreation = (
-        "PO-Line creation",
-        "Report on how PO-Line creation went, what merge criterias were used, etc",
-    )
-    AcquisitionMethodMapping = ("POL Acquisition Method Mapping", "")
-    MalformedInterfaceUri = (
-        "Malformed interface URIs",
-        "Interfaces with malformed URIs will not be migrated. See data issues log.\nFOLIO Interface URIs must start with one of the following: 'ftp://', 'sftp://', 'http://', 'https://'.",
-    )
-    FieldMappingDetails = ("Mapping details", "")
-    CatalogingAgency = ("Cataloging sources", "")
-    Trivia = ("Trivia", "")
-    LeaderManipulation = ("Leader manipulation", "")
-    TermsMapping = ("Terms Mapping", "Reference data mapping for Terms.")
-    FeeFineOnwerMapping = ("Fee/Fine Owner mapping", "Reference data mapping for Fee/Fine owners.")
-    FeeFineTypesMapping = ("Fee/Fine Type mapping", "Reference data mapping for Fee/Fine types.")
-    FeeFineServicePointTypesMapping = (
-        "Fee/Fine Service Point mapping",
-        "Reference data mapping for Fee/Fine service points.",
-    )
-    CategoriesMapping = (
-        "Organization contact categories",
-        "Reference data mapping for contacts, addresses, emails, and phones numbers.",
-    )
-    OrganizationTypeMapping = (
-        "Organization types",
-        "Reference data mapping for Organization types.",
-    )
-
-    DateTimeConversions = (
-        "DateTime conversions",
-        "Some date and date time strings are converted to UTC DateTime objects and then printed accoding to ISO standard.",
-    )
-    Details = ("Details", "")
-    MarcValidation = ("MARC21 validation issues found in records", "")
-    Introduction = (
-        "Introduction",
-        "<br/>Data errors preventing records from being migrated are marked **FIX BEFORE MIGRATION**. The library is advised to clean up these errors in the source data.<br/><br/> The sections related to field counts and mapping results are marked **REVIEW**. These do not indicate errors preventing records from being migrated, but may point to data anomalies or in the mappings. The library should review these to make sure that the numbers are what one would expect, knowing the source data. Is this the expected number of serials? Is this the expected number of cartographic materials?",
-    )
-    DiscardedLoans = ("Discarded loans", "List of loans discarded for various resons")
-    DiscardedReserves = ("Discarded reserves", "List of reserves discarded for various resons")
-    DiscardedRequests = (
-        "Discarded Requests",
-        "List of requests discarded for various resons",
-    )
-    HoldingsMerging = ("Holdings Merging", "")
-    DepartmentsMapping = ("Departments mappings", "")
-    MissingRequiredProperties = ("Missing or empty required properties", "")
-    MappedLegacyFields = (
-        "Mapped Legacy Fields",
-        "Library action: **REVIEW** <br/>This table lists all the MARC fields in the source data, and whether it has been mapped to a FOLIO instance record field. The library should examine the MARC tags with a high 'Unmapped' figure and determine if these MARC tags contain data that you would like mapped to the FOLIO instance record.",
-    )
-    MappedFolioFields = (
-        "Mapped FOLIO fields",
-        "Library action: **REVIEW** <br/>This table shows how many of the FOLIO instance records created contain data in the different FOLIO fields. The library should review the mapped totals against what they would expect to see mapped.",
-    )
-    Section1 = (
-        "__Section 1: instances",
-        "This entries below seem to be related to instances",
-    )
-    RecordStatus = (
-        "Record status (leader pos 5)",
-        "Library action: **All values that are not a, c, d, n or p will be set to c. If this is not what you want, you need to correct these values in your system. **<br/>An overview of the Record statuses (Leader position 5) present in your source data.    Pay attention to the number of occurrences of the value 'd'. These d's are expressing that they are deleted, and the records might not work as expected in FOLIO. Consider marking them as suppressed in your current system and export them as a separate batch in order to have them suppressed in FOLIO. Allowed values according to the MARC standard are a,c,d,n,p",
-    )
-    Suppression = (
-        "Suppression",
-        "What records got assigned what suppression setting in the records.",
-    )
-    ValueErrors = (
-        "Records failed to migrate due to Value errors found in Transformation",
-        "**FIX BEFORE MIGRATION** This section identifies records that have unexpected or missing values that prevent the transformation. The type of error will be specified. The library must resolve the issue for the record to be migrated.",
-    )
-    MissingTitles = (
-        "Records without titles",
-        "**FIX IN SOURCE DATA** These records are missing a 245 field. FOLIO requires an instance title. The library must enter this information for the record to be migrated.",
-    )
-    MissingInstanceTypeIds = (
-        "Records without Instance Type Ids",
-        "**IC ACTION REQUIRED** These reords should get an instance type ID mapped from 336, or a default of Undefined, or they will not be transformed.",
-    )
-    MappedInstanceFormats = (
-        "Mapped instance formats",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Instance format values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    MappedIdentifierTypes = (
-        "Mapped identifier types",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Identifier type values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    MappedNoteTypes = (
-        "Mapped note types",
-        "Library action: **REVIEW** <br/>The created FOLIO records contain the following Note type values.  <br/>The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    MappedContributorNameTypes = (
-        "Mapped contributor name types",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Name type values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    UnmappedContributorNameTypes = (
-        "Unmapped contributor name types",
-        "**REVIEW/IC ACTION REQUIRED** <br/>Contributor name types present in the source data, but not mapped to a FOLIO value. The library and IC should review values and mapping.",
-    )
-    ContributorTypeMapping = (
-        "Contributor type mapping",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Contributor type values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    MappedElectronicRelationshipTypes = (
-        "Mapped electronic access relationships types",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Electronic access relationship type values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    IncompleteEntityMapping = (
-        "Incomplete entity mapping adding entity",
-        "**NO ACTION REQUIRED** <br/>This is a coding anomaly that FSE will look into.  <br/>Usually, the library does not have to do anything about it.<br/> One thing to look at is if there are many repeated subfields or unexpected patterns of subfields in the table.",
-    )
-    RecourceTypeMapping = (
-        "Resource Type Mapping (336)",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Instance type values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    MappedAlternativeTitleTypes = (
-        "Mapped Alternative title types",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Alternative title type values. The library should review the total number for each value against what they would expect to see mapped. The FOLIO community recommends a coarse-grained mapping.",
-    )
-    Field880Mappings = (
-        "880 mappings",
-        "This table shows how the 880 (Alternate Graphic Representation) has been mapped.",
-    )
-    Field880MappingsMissingFromRules = (
-        "880 mappings: mapped field not in mapping-rules",
-        "Library action: **REVIEW** <br/>Fields that are referenced in the 880 mapping, but not configured in the mapping-rules.",
-    )
-    InstanceLevelCallnumber = (
-        "Instance level callNumber",
-        "Library action: **REVIEW** <br/>True if the source data contains bib level call numbers in MARC field 099.",
-    )
-    NonNumericTagsInRecord = (
-        "Non-numeric tags in records",
-        "Library action: **REVIEW** <br/>Non-numeric tags may indicate locally defined fields.",
-    )
-    MatchedModesOfIssuanceCode = (
-        "Matched Modes of issuance code",
-        "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Mode of issuace values. The library should review the total number for each value against what they would expect to see mapped.",
-    )
-    UnrecognizedLanguageCodes = (
-        "Unrecognized language codes in records",
-        "Library action: **REVIEW** <br/>Language code values in the source data that do not match standard language codes. If not fixed before migration, these will display as Undetermined in the instance record and Filtering by language in Inventory will not be conclusive.",
-    )
-    Section2 = (
-        "__Section 2: holdings",
-        "The entries below seem to be related to holdings",
-    )
-    HoldingsTypeMapping = ("Holdings type mapping", "")
-    LegacyLocationCodes = ("Legacy location codes", "")
-    Locations = (
-        "Locations - Unmapped legacy codes",
-        "",
-    )
-    UserGroupMapping = ("User group mapping", "")
-    DefaultValuesAdded = (
-        "Default values from mapping added",
-        "The values below were added to all records from the 'value' field in the mapping file, overriding any mapped values from the source data.",
-    )
-    FolioDefaultValuesAdded = (
-        "FOLIO default values added",
-        "The below FOLIO default values were added to records that had no mapped value in the source data.",
-    )
-    UsersPerPatronType = ("Users per patron type", "")
-    MappedLocations = ("Mapped Locations", "")
-    Leader06 = ("Leader 06 (Holdings type)", "")
-    Section3 = ("__Section 3: items", "The entries below seem to be related to items")
-    Exceptions = ("Exceptions", "")
-    HridHandling = (
-        "HRID and 001/035 handling",
-        "There are two ways of handling HRIDs. The default behaviour is to take the current 001 and move that to a new 035. This will also emerge as an Identifier on the Inventory Instances. The 001 and Instance HRID will be generated from the HRID settings in FOLIO. The second option is to maintain the 001s in the records, and also add this as the Instance HRID",
-    )
-    PrecedingSuccedingTitles = (
-        "Preceding and Succeeding titles",
-        "",
-    )
-    HoldingsGenerationFromBibs = (
-        "Holdings generation from bibs",
-        "Some libraries have Holdings/MFHD information baked into their bib records. The following breakdown gives an idea on the occurrence of 852/866 combinations",
-    )
-    InstanceFormat = (
-        "Instance format ids handling (337 + 338))",
-        "",
-    )
-    MappedClassificationTypes = (
-        "Mapped classification types",
-        "",
-    )
-    LocationMapping = (
-        "Location mapping",
-        "These are the results for the mapping between legacy locations and your new FOLIO location structure",
-    )
-    ValueSetInMappingFile = (
-        "Value set in mapping file",
-        "The value for these fields are set in the mapping file instead of coming from the legacy system data.",
-    )
-    ValuesMappedFromLegacyFields = (
-        "Values mapped from legacy fields",
-        "A list fo the values and what they were mapped to",
-    )
-    GeneralStatistics = (
-        "General statistics",
-        "A list of general counters to outline the transformation as a whole.",
-    )
-    MappedPublisherRoleFromIndicator2 = (
-        "Mapped publisher role from Indicator2",
-        "Publication Role, taken from the code in Ind2",
-    )
-    CallNumberTypeMapping = (
-        "Callnumber type mapping",
-        "Call number types in MFHDs are mapped from 852, Indicator 1 according to a certain scheme. (LOC documentation)[https://www.loc.gov/marc/holdings/hd852.html]",
-    )
-    LanguagesInRecords = (
-        "Language codes in records",
-        "A breakdown of language codes occuring in the records. Purely informational.",
-    )
-    ReceiptStatusMapping = (
-        "Reciept status mapping",
-        "Mapped reciept status from 008[06]. (LoC documentation)[https://www.loc.gov/marc/holdings/hd008.html]",
-    )
-    LanguageCodeSources = (
-        "Language code sources in 041",
-        'Most language codes in MARC records come from this list: https://www.loc.gov/marc/languages/, But other codes are allowed. The controlled vocabulary needs to be stated in $2 and this is where the information in this list comes from. The mapping tools can not currently handle other language codes, and so cannot FOLIO. <br/>One solution is to migrate the codes into a string or/and replace the language code with the parent language (these languages are usually subgroups of other languages). Many times, these records already have the "super-language" recorded as well. The data will remain in the MARC records.',
-    )
-    FailedFiles = ("Failed files", "")
-    BoundWithMappings = ("Bound-with mapping", "")
-    TemporaryLocationMapping = ("Temporary location mapping", "")
-    TemporaryLoanTypeMapping = ("Temporary Loan type mapping", "")
-    PermanentLoanTypeMapping = ("Permanent Loan type mapping", "")
-    OrderLineLocationMapping = (
-        "POL location mapping",
-        "This is the location for for the purchase order line.",
-    )
-    PurchaseOrderVendorLinking = (
-        "Linked Organizations",
-        "All purchase orders MUST be linked to an organization.",
-    )
-    PurchaseOrderInstanceLinking = (
-        "Linked Instances",
-        "Purchase Order Lines can but do not have to be linked to instances",
-    )
-    StatisticalCodeMapping = ("Statistical code mapping", "")
-    HoldingsRecordIdMapping = ("Holdings IDs mapped", "")
-    UnmappedProperties = ("Unmapped properties", "")
-    StatusMapping = ("Status mapping", "")
-    ReferenceDataMapping = ("Reference Data Mapping", "")
-    FieldMappingErrors = ("Field mapping errors", "")
-    AddedValueFromParameter = ("Added value from parameter since value is empty", "")
-    InstanceTypeMapping = ("Instance Type Mapping", "")
-    StaffOnlyViaIndicator = ("Set note to staff only via indicator", "")
-    PossibleCleaningTasks = ("Possible cleaning tasks", "")
-    HoldingsStatementsParsing = (
-        "Parsed Holdings statements from 85X/86X combinations",
-        "",
-    )
-    MaterialTypeMapping = ("Mapped Material Types", "")
-    AuthoritySourceFileMapping = (
-        "Authority Source File Mapping Results",
-        "Mappings based on FOLIO authority `naturalId` alpha prefix",
-    )
-    AuthorityEncodingLevel = (
-        "Encoding level (leader pos 17)",
-        "Library action: **All values that are not n or o will be set to n. If this is not what you want, you need to correct these values in your system. **<br/>An overview of the Encoding levels (Leader position 17) present in your source data.  Allowed values according to the MARC standard are n or o",
-    )
diff --git a/src/folio_migration_tools/transaction_migration/legacy_loan.py b/src/folio_migration_tools/transaction_migration/legacy_loan.py
index 9ba33ade..63736ccc 100644
--- a/src/folio_migration_tools/transaction_migration/legacy_loan.py
+++ b/src/folio_migration_tools/transaction_migration/legacy_loan.py
@@ -6,7 +6,6 @@
 from dateutil.parser import parse
 
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 
 utc = ZoneInfo("UTC")
 
@@ -132,4 +131,4 @@ def make_utc(self):
             self.errors.append(("UTC correction issues", "both dates"))
 
     def report(self, what_to_report: str):
-        self.migration_report.add(Blurbs.Details, what_to_report)
+        self.migration_report.add("Details", what_to_report)
diff --git a/translations/en.json b/translations/en.json
new file mode 100644
index 00000000..c755371a
--- /dev/null
+++ b/translations/en.json
@@ -0,0 +1,444 @@
+{
+  "$0 base uri or source code": "$0 base uri or source code",
+  "%{action} error. http status: %{status}": "%{action} error. http status: %{status}",
+  "%{action} error: %{message}": "%{action} error: %{message}",
+  "%{fields_criteria} empty or not set": "%{fields_criteria} empty or not set",
+  "%{field} a,x and z are all empty": "%{field} a,x and z are all empty",
+  "%{field} subfields a, x, and z missing from field": "%{field} subfields a, x, and z missing from field",
+  "%{fro} mapped from %{record}": "%{fro} mapped from %{record}",
+  "%{props} were concatenated": "%{props} were concatenated",
+  "%{schema_value} added to %{prop_name}": "%{schema_value} added to %{prop_name}",
+  "%{tag} subfield %{subfield} not in field": "%{tag} subfield %{subfield} not in field",
+  "%{value} added to %{entry}": "%{value} added to %{entry}",
+  "%{value} mapped to %{mapped_value}": "%{value} mapped to %{mapped_value}",
+  "%{value} not found in map.": "%{value} not found in map.",
+  "008 is too short": "008 is too short",
+  "008 length invalid. '%{rest}' was stripped out": "008 length invalid. '%{rest}' was stripped out",
+  "035 generation from 001 turned off": "035 generation from 001 turned off",
+  "1 is public, all other values are Staff only": "1 is public, all other values are Staff only",
+  "338$b is missing. Will try parse from 337$a and 338$a": "338$b is missing. Will try parse from 337$a and 338$a",
+  "Added 035 from 001": "Added 035 from 001",
+  "Additional 852$b was moved to 852$x": "Additional 852$b was moved to 852$x",
+  "Aged to lost and checked out": "Aged to lost and checked out",
+  "Already set to %{value}. %{leader_key} was %{leader}": "Already set to %{value}. %{leader_key} was %{leader}",
+  "An Unmapped": "An Unmapped",
+  "Authority records transformation report": "Authority records transformation report",
+  "BW Items found tied to previously created BW Holdings": "BW Items found tied to previously created BW Holdings",
+  "Bib identifier not in instances_id_map, no instance linked": "Bib identifier not in instances_id_map, no instance linked",
+  "Bib ids referenced in bound-with items": "Bib ids referenced in bound-with items",
+  "Bibliographic records transformation report": "Bibliographic records transformation report",
+  "Bound-with holdings created": "Bound-with holdings created",
+  "Bound-with items callnumber identified": "Bound-with items callnumber identified",
+  "Bound-with items identified by bib id": "Bound-with items identified by bib id",
+  "Change due date error": "Change due date error",
+  "Changed %{a} to %{b}": "Changed %{a} to %{b}",
+  "Check mapping file against the schema.": "Check mapping file against the schema.",
+  "Checked out on first try": "Checked out on first try",
+  "Checked out on second try": "Checked out on second try",
+  "Click to expand all %{count} things": {
+    "many": "Click to expand all %{count} things",
+    "one": "Click to expand one thing"
+  },
+  "Click to expand field report": "Click to expand field report",
+  "Code %{code} ('%{code_raw}') not found in FOLIO ": "Code %{code} ('%{code_raw}') not found in FOLIO ",
+  "Code '%{code}' not found in FOLIO": "Code '%{code}' not found in FOLIO",
+  "Code from 338$b NOT found in FOLIO: \"%{value}\"": "Code from 338$b NOT found in FOLIO: \"%{value}\"",
+  "Condition in rules hit": "Condition in rules hit",
+  "Contributor type code \"%{code}\" found for $%{code_subfield}": "Contributor type code \"%{code}\" found for $%{code_subfield}",
+  "Contributor type name %{name} found for %{tag}": "Contributor type name %{name} found for %{tag}",
+  "Could not parse catalogedDate": "Could not parse catalogedDate",
+  "Count": "Count",
+  "Courses migration report": "Courses migration report",
+  "Created HRID using default settings": "Created HRID using default settings",
+  "DATA ISSUE Invalid dates": "DATA ISSUE Invalid dates",
+  "DATA ISSUE Invalid sum": "DATA ISSUE Invalid sum",
+  "DATA ISSUE Items not in FOLIO": "DATA ISSUE Items not in FOLIO",
+  "DATA ISSUE Users not in FOLIO": "DATA ISSUE Users not in FOLIO",
+  "Data issue. Consider fixing the record. ": "Data issue. Consider fixing the record. ",
+  "Declare item as lost": "Declare item as lost",
+  "Discarded reserves": "Discarded reserves",
+  "Duplicate 001. Creating HRID instead.\n Previous 001 will be stored in a new 035 field": "Duplicate 001. Creating HRID instead.\n Previous 001 will be stored in a new 035 field",
+  "Duplicate MARC record identifiers ": "Duplicate MARC record identifiers ",
+  "Duplicate barcodes": "Duplicate barcodes",
+  "Duplicate key based on current merge criteria. Records merged": "Duplicate key based on current merge criteria. Records merged",
+  "Duplicate loans (or failed twice)": "Duplicate loans (or failed twice)",
+  "Elapsed time:": "Elapsed time:",
+  "Encoding errors": "Encoding errors",
+  "Error setting item status to %{status}": "Error setting item status to %{status}",
+  "FAILED Records failed due to an error": "FAILED Records failed due to an error",
+  "FOLIO Field": "FOLIO Field",
+  "Failed 1st time. No retries": "Failed 1st time. No retries",
+  "Failed checkout http status %{code}": "Failed checkout http status %{code}",
+  "Failed loans": "Failed loans",
+  "Failed records. No unique record identifiers in legacy record": "Failed records. No unique record identifiers in legacy record",
+  "Failed user transformations": "Failed user transformations",
+  "Failure to post reserve": "Failure to post reserve",
+  "Fallback mapping": "Fallback mapping",
+  "Field Mapping Errors found": "Field Mapping Errors found",
+  "Generic exceptions (see log for details)": "Generic exceptions (see log for details)",
+  "Had migrated user barcode": "Had migrated user barcode",
+  "Handled inactive users": "Handled inactive users",
+  "Holding prevented from merging by holdingsTypeId": "Holding prevented from merging by holdingsTypeId",
+  "Holdings Records Written to disk": "Holdings Records Written to disk",
+  "Holdings already created from Item": "Holdings already created from Item",
+  "Holdings lookups performed": "Holdings lookups performed",
+  "Holdings transformation report": "Holdings transformation report",
+  "Instances HRID starting number": "Instances HRID starting number",
+  "Instances linked using instances_id_map": "Instances linked using instances_id_map",
+  "Interfaces": "Interfaces",
+  "Inventory records written to disk": "Inventory records written to disk",
+  "Item lookups performed": "Item lookups performed",
+  "Item transformation report": "Item transformation report",
+  "Items already detected as missing": "Items already detected as missing",
+  "Legacy Field": "Legacy Field",
+  "Legacy bib records without 001": "Legacy bib records without 001",
+  "Legacy id is empty": "Legacy id is empty",
+  "Loan already in failed.": "Loan already in failed.",
+  "Loans discarded. Had migrated item barcode": "Loans discarded. Had migrated item barcode",
+  "Loans failed pre-validation": "Loans failed pre-validation",
+  "Loans migration report": "Loans migration report",
+  "Loans verified against migrated user and item": "Loans verified against migrated user and item",
+  "MFHD records transformation report": "MFHD records transformation report",
+  "Manual fee/fine transformation report": "Manual fee/fine transformation report",
+  "Mapped": "Mapped",
+  "Mapped FOLIO fields": "Mapped FOLIO fields",
+  "Mapped Legacy fields": "Mapped Legacy fields",
+  "Mapped from Indicator 1": "Mapped from Indicator 1",
+  "Mapping failed for %{tag} \"%{subfield}\" (%{normalized_subfield})": "Mapping failed for %{tag} \"%{subfield}\" (%{normalized_subfield})",
+  "Mapping failed for %{tag} \"%{subfield}\" (Normalized: %{normalized_subfield})": "Mapping failed for %{tag} \"%{subfield}\" (Normalized: %{normalized_subfield})",
+  "Mapping not set up for target field": "Mapping not set up for target field",
+  "Mapping not setup": "Mapping not setup",
+  "Measure": "Measure",
+  "Missing Instructors": "Missing Instructors",
+  "No Call Number Type Mapping": "No Call Number Type Mapping",
+  "No Leader[7] in": "No Leader[7] in",
+  "No corresponding $b in corresponding 338": "No corresponding $b in corresponding 338",
+  "No corresponding 337 to 338 even though 338$b was one character": "No corresponding 337 to 338 even though 338$b was one character",
+  "No item with barcode found in FOLIO": "No item with barcode found in FOLIO",
+  "No user with barcode found in FOLIO": "No user with barcode found in FOLIO",
+  "Number of Legacy items in %{container}": "Number of Legacy items in %{container}",
+  "Number of Legacy items in file": "Number of Legacy items in file",
+  "Number of Legacy items in total": "Number of Legacy items in total",
+  "Number of bib records referenced in item": "Number of bib records referenced in item",
+  "Number of discarded notes with no content": "Number of discarded notes with no content",
+  "Number of files processed": "Number of files processed",
+  "Number of linked %{type} created": "Number of linked %{type} created",
+  "Number of linked notes created": "Number of linked notes created",
+  "Number of objects in source data file": "Number of objects in source data file",
+  "Number of organizations created": "Number of organizations created",
+  "Number of records written to disk": "Number of records written to disk",
+  "Number of reoccuring identical %{type}": "Number of reoccuring identical %{type}",
+  "Ogranization transformation report": "Ogranization transformation report",
+  "Organizations linked using organizations_id_map": "Organizations linked using organizations_id_map",
+  "Organizations not in ID map, linked using FOLIO lookup": "Organizations not in ID map, linked using FOLIO lookup",
+  "Original value": "Original value",
+  "Other checkout failure": "Other checkout failure",
+  "Parsing error! %{prop_name}: %{value}. The empty string was returned": "Parsing error! %{prop_name}: %{value}. The empty string was returned",
+  "Patron barcode": "Patron barcode",
+  "Patron barcode already detected as missing": "Patron barcode already detected as missing",
+  "Patron barcode not in FOLIO": "Patron barcode not in FOLIO",
+  "Patron lookups performed": "Patron lookups performed",
+  "Posted reserves": "Posted reserves",
+  "Present": "Present",
+  "Previously transformed holdings record loaded": "Previously transformed holdings record loaded",
+  "Processed pre-validated loans": "Processed pre-validated loans",
+  "Processed reserves": "Processed reserves",
+  "Pruchase Orders and Purchase Order Lines Transformation Report": "Pruchase Orders and Purchase Order Lines Transformation Report",
+  "RECORD FAILED Organization identifier not in ID map/FOLIO": "RECORD FAILED Organization identifier not in ID map/FOLIO",
+  "Records failed": "Records failed",
+  "Records failed because of failed holdings": "Records failed because of failed holdings",
+  "Records failed due to an error. See data issues log for details": "Records failed due to an error. See data issues log for details",
+  "Records in file before parsing": "Records in file before parsing",
+  "Records matched to Instances": "Records matched to Instances",
+  "Records not matched to Instances": "Records not matched to Instances",
+  "Records successfully decoded from MARC21": "Records successfully decoded from MARC21",
+  "Records that failed transformation. Check log for details": "Records that failed transformation. Check log for details",
+  "Records with %{has_many}s but no %{has_no}": "Records with %{has_many}s but no %{has_no}",
+  "Records with both %{has_many}s and at least one %{has_one}": "Records with both %{has_many}s and at least one %{has_one}",
+  "Records with encoding errors - parsing failed": "Records with encoding errors - parsing failed",
+  "Records with unexpected length in $6": "Records with unexpected length in $6",
+  "Records without $6": "Records without $6",
+  "Records without %{has_no}s but with %{has}": "Records without %{has_no}s but with %{has}",
+  "Requests discarded. Had migrated item barcode: %{item_barcode}.\n Had migrated user barcode: %{patron_barcode}": "Requests discarded. Had migrated item barcode: %{item_barcode}.\n Had migrated user barcode: %{patron_barcode}",
+  "Requests in file": "Requests in file",
+  "Requests migration report": "Requests migration report",
+  "Requests successfully verified against migrated users and items": "Requests successfully verified against migrated users and items",
+  "Requests that failed verification against migrated users and items": "Requests that failed verification against migrated users and items",
+  "Requests with valid source data": "Requests with valid source data",
+  "Requests with valueErrors": "Requests with valueErrors",
+  "Reserve discarded. Could not find migrated barcode": "Reserve discarded. Could not find migrated barcode",
+  "Reserve verified against migrated item": "Reserve verified against migrated item",
+  "Reserves migration report": "Reserves migration report",
+  "Rows merged to create Purchase Orders": "Rows merged to create Purchase Orders",
+  "SRS records written to disk": "SRS records written to disk",
+  "Second failure": "Second failure",
+  "Set 852 to FOLIO location code": "Set 852 to FOLIO location code",
+  "Set _version to -1 to enable upsert": "Set _version to -1 to enable upsert",
+  "Set leader 09 (Character coding scheme) from %{field} to a": "Set leader 09 (Character coding scheme) from %{field} to a",
+  "Set leader 10 (Indicator count) from %{field} to 2": "Set leader 10 (Indicator count) from %{field} to 2",
+  "Set leader 11 (Subfield code count) from %{record} to 2": "Set leader 11 (Subfield code count) from %{record} to 2",
+  "Set leader 20-23 from %{field} to 4500": "Set leader 20-23 from %{field} to 4500",
+  "Source digits": "Source digits",
+  "Source of heading or term": "Source of heading or term",
+  "Staff suppressed": "Staff suppressed",
+  "Stored courselistings": "Stored courselistings",
+  "Stored courses": "Stored courses",
+  "Stored instructors": "Stored instructors",
+  "Subfield b not in 336": "Subfield b not in 336",
+  "Successful match": "Successful match",
+  "Successful matching on %{criteria_1} and %{criteria_2}": "Successful matching on %{criteria_1} and %{criteria_2}",
+  "Successful matching on %{criteria}": "Successful matching on %{criteria}",
+  "Successful user transformations": "Successful user transformations",
+  "Successfully %{action}": "Successfully %{action}",
+  "Successfully activated user": "Successfully activated user",
+  "Successfully changed due date": "Successfully changed due date",
+  "Successfully checked out": "Successfully checked out",
+  "Successfully deactivated user": "Successfully deactivated user",
+  "Successfully declared loan as Claimed returned": "Successfully declared loan as Claimed returned",
+  "Successfully declared loan as lost": "Successfully declared loan as lost",
+  "Successfully migrated requests": "Successfully migrated requests",
+  "Successfully posted reserves": "Successfully posted reserves",
+  "Successfully set item status to %{status}": "Successfully set item status to %{status}",
+  "Successfully updated open loan": "Successfully updated open loan",
+  "Suppressed from discovery": "Suppressed from discovery",
+  "TOTAL Accounts created": "TOTAL Accounts created",
+  "TOTAL Feefineactions created": "TOTAL Feefineactions created",
+  "TOTAL Purchase Order Lines created": "TOTAL Purchase Order Lines created",
+  "TOTAL Purchase Orders created": "TOTAL Purchase Orders created",
+  "Tag: %{tag}. Added value: %{value}": "Tag: %{tag}. Added value: %{value}",
+  "Target field": "Target field",
+  "Target string '%{string}' not in Schema!": "Target string '%{string}' not in Schema!",
+  "Target type": "Target type",
+  "Time Finished:": "Time Finished:",
+  "Time Started:": "Time Started:",
+  "Timings": "Timings",
+  "Took HRID from 001": "Took HRID from 001",
+  "Total number of Tags processed": "Total number of Tags processed",
+  "Transformation process error": "Transformation process error",
+  "Unhandled call number type in $2 (ind1 == 7)": "Unhandled call number type in $2 (ind1 == 7)",
+  "Unhandled call number type in ind1: \"%{ind1}\".\n Returning default Callnumber type: %{type}": "Unhandled call number type in ind1: \"%{ind1}\".\n Returning default Callnumber type: %{type}",
+  "Unique BW Holdings created from Items": "Unique BW Holdings created from Items",
+  "Unique Holdings created from Items": "Unique Holdings created from Items",
+  "Unique ID:s written to legacy map": "Unique ID:s written to legacy map",
+  "Unmapped": "Unmapped",
+  "Unmapped code": "Unmapped code",
+  "Unmatched level": "Unmatched level",
+  "Unsuccessful matching on %{criteria_1} and %{criteria_2}": "Unsuccessful matching on %{criteria_1} and %{criteria_2}",
+  "Unsuccessful matching on %{criteria}": "Unsuccessful matching on %{criteria}",
+  "Unsuccessfully declared loan %{loan} as Claimed returned": "Unsuccessfully declared loan %{loan} as Claimed returned",
+  "Unsuccessfully declared loan as lost": "Unsuccessfully declared loan as lost",
+  "Unsuccessfully migrated requests": "Unsuccessfully migrated requests",
+  "Update item": "Update item",
+  "Update open loan error http status": "Update open loan error http status",
+  "Update user": "Update user",
+  "Updated renewal count for loan": "Updated renewal count for loan",
+  "Users already detected as missing": "Users already detected as missing",
+  "Users transformation report": "Users transformation report",
+  "Valid, prepared requests, ready for posting": "Valid, prepared requests, ready for posting",
+  "Value": "Value",
+  "Values in %{field}": "Values in %{field}",
+  "blurbs..description": "",
+  "blurbs..title": "",
+  "blurbs.AcquisitionMethodMapping.description": "",
+  "blurbs.AcquisitionMethodMapping.title": "POL Acquisition Method Mapping",
+  "blurbs.AddedValueFromParameter.description": "",
+  "blurbs.AddedValueFromParameter.title": "Added value from parameter since value is empty",
+  "blurbs.AuthorityEncodingLevel.description": "Library action: **All values that are not n or o will be set to n. If this is not what you want, you need to correct these values in your system. **<br/>An overview of the Encoding levels (Leader position 17) present in your source data.  Allowed values according to the MARC standard are n or o",
+  "blurbs.AuthorityEncodingLevel.title": "Encoding level (leader pos 17)",
+  "blurbs.AuthoritySourceFileMapping.description": "Mappings based on FOLIO authority `naturalId` alpha prefix",
+  "blurbs.AuthoritySourceFileMapping.title": "Authority Source File Mapping Results",
+  "blurbs.AuthoritySources.description": "",
+  "blurbs.AuthoritySources.title": "Authorization sources and related information",
+  "blurbs.BoundWithMappings.description": "",
+  "blurbs.BoundWithMappings.title": "Bound-with mapping",
+  "blurbs.CallNumberTypeMapping.description": "Call number types in MFHDs are mapped from 852, Indicator 1 according to a certain scheme. (LOC documentation)[https://www.loc.gov/marc/holdings/hd852.html]",
+  "blurbs.CallNumberTypeMapping.title": "Callnumber type mapping",
+  "blurbs.CatalogingAgency.description": "",
+  "blurbs.CatalogingAgency.title": "Cataloging sources",
+  "blurbs.CategoriesMapping.description": "Reference data mapping for contacts, addresses, emails, and phones numbers.",
+  "blurbs.CategoriesMapping.title": "Organization contact categories",
+  "blurbs.ContributorTypeMapping.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Contributor type values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.ContributorTypeMapping.title": "Contributor type mapping",
+  "blurbs.DateTimeConversions.description": "Some date and date time strings are converted to UTC DateTime objects and then printed accoding to ISO standard.",
+  "blurbs.DateTimeConversions.title": "DateTime conversions",
+  "blurbs.DefaultValuesAdded.description": "The values below were added to all records from the 'value' field in the mapping file, overriding any mapped values from the source data.",
+  "blurbs.DefaultValuesAdded.title": "Default values from mapping added",
+  "blurbs.DepartmentsMapping.description": "",
+  "blurbs.DepartmentsMapping.title": "Departments mappings",
+  "blurbs.Details.description": "",
+  "blurbs.Details.title": "Details",
+  "blurbs.DiffsBetweenOrders.description": "This is a technical report that helps you to identify differences in the mapped order fields. ",
+  "blurbs.DiffsBetweenOrders.title": "Differences between generated orders with same Legacy Identifier",
+  "blurbs.DiscardedLoans.description": "List of loans discarded for various resons",
+  "blurbs.DiscardedLoans.title": "Discarded loans",
+  "blurbs.DiscardedRequests.description": "List of requests discarded for various resons",
+  "blurbs.DiscardedRequests.title": "Discarded Requests",
+  "blurbs.DiscardedReserves.description": "List of reserves discarded for various resons",
+  "blurbs.DiscardedReserves.title": "Discarded reserves",
+  "blurbs.Exceptions.description": "",
+  "blurbs.Exceptions.title": "Exceptions",
+  "blurbs.FailedFiles.description": "",
+  "blurbs.FailedFiles.title": "Failed files",
+  "blurbs.FeeFineOnwerMapping.description": "Reference data mapping for Fee/Fine owners.",
+  "blurbs.FeeFineOnwerMapping.title": "Fee/Fine Owner mapping",
+  "blurbs.FeeFineServicePointTypesMapping.description": "Reference data mapping for Fee/Fine service points.",
+  "blurbs.FeeFineServicePointTypesMapping.title": "Fee/Fine Service Point mapping",
+  "blurbs.FeeFineTypesMapping.description": "Reference data mapping for Fee/Fine types.",
+  "blurbs.FeeFineTypesMapping.title": "Fee/Fine Type mapping",
+  "blurbs.Field880Mappings.description": "This table shows how the 880 (Alternate Graphic Representation) has been mapped.",
+  "blurbs.Field880Mappings.title": "880 mappings",
+  "blurbs.Field880MappingsMissingFromRules.description": "Library action: **REVIEW** <br/>Fields that are referenced in the 880 mapping, but not configured in the mapping-rules.",
+  "blurbs.Field880MappingsMissingFromRules.title": "880 mappings: mapped field not in mapping-rules",
+  "blurbs.FieldMappingDetails.description": "",
+  "blurbs.FieldMappingDetails.title": "Mapping details",
+  "blurbs.FieldMappingErrors.description": "",
+  "blurbs.FieldMappingErrors.title": "Field mapping errors",
+  "blurbs.FolioDefaultValuesAdded.description": "The below FOLIO default values were added to records that had no mapped value in the source data.",
+  "blurbs.FolioDefaultValuesAdded.title": "FOLIO default values added",
+  "blurbs.GeneralStatistics.description": "A list of general counters to outline the transformation as a whole.",
+  "blurbs.GeneralStatistics.title": "General statistics",
+  "blurbs.HoldingsGenerationFromBibs.description": "Some libraries have Holdings/MFHD information baked into their bib records. The following breakdown gives an idea on the occurrence of 852/866 combinations",
+  "blurbs.HoldingsGenerationFromBibs.title": "Holdings generation from bibs",
+  "blurbs.HoldingsMerging.description": "",
+  "blurbs.HoldingsMerging.title": "Holdings Merging",
+  "blurbs.HoldingsRecordIdMapping.description": "",
+  "blurbs.HoldingsRecordIdMapping.title": "Holdings IDs mapped",
+  "blurbs.HoldingsStatementsParsing.description": "",
+  "blurbs.HoldingsStatementsParsing.title": "Parsed Holdings statements from 85X/86X combinations",
+  "blurbs.HoldingsTypeMapping.description": "",
+  "blurbs.HoldingsTypeMapping.title": "Holdings type mapping",
+  "blurbs.HridHandling.description": "There are two ways of handling HRIDs. The default behaviour is to take the current 001 and move that to a new 035. This will also emerge as an Identifier on the Inventory Instances. The 001 and Instance HRID will be generated from the HRID settings in FOLIO. The second option is to maintain the 001s in the records, and also add this as the Instance HRID",
+  "blurbs.HridHandling.title": "HRID and 001/035 handling",
+  "blurbs.IncompleteEntityMapping.description": "**NO ACTION REQUIRED** <br/>This is a coding anomaly that FSE will look into.  <br/>Usually, the library does not have to do anything about it.<br/> One thing to look at is if there are many repeated subfields or unexpected patterns of subfields in the table.",
+  "blurbs.IncompleteEntityMapping.title": "Incomplete entity mapping adding entity",
+  "blurbs.IncompleteSubPropertyRemoved.description": "Add the missing required information to the record in your current ILS to ensure that it can be migrated over.",
+  "blurbs.IncompleteSubPropertyRemoved.title": "Sub-property removed due to missing required fields",
+  "blurbs.InstanceFormat.description": "",
+  "blurbs.InstanceFormat.title": "Instance format ids handling (337 + 338))",
+  "blurbs.InstanceLevelCallnumber.description": "Library action: **REVIEW** <br/>True if the source data contains bib level call numbers in MARC field 099.",
+  "blurbs.InstanceLevelCallnumber.title": "Instance level callNumber",
+  "blurbs.InstanceTypeMapping.description": "",
+  "blurbs.InstanceTypeMapping.title": "Instance Type Mapping",
+  "blurbs.Introduction.description": "<br/>Data errors preventing records from being migrated are marked **FIX BEFORE MIGRATION**. The library is advised to clean up these errors in the source data.<br/><br/> The sections related to field counts and mapping results are marked **REVIEW**. These do not indicate errors preventing records from being migrated, but may point to data anomalies or in the mappings. The library should review these to make sure that the numbers are what one would expect, knowing the source data. Is this the expected number of serials? Is this the expected number of cartographic materials?",
+  "blurbs.Introduction.title": "Introduction",
+  "blurbs.LanguageCodeSources.description": "Most language codes in MARC records come from this list: https://www.loc.gov/marc/languages/, But other codes are allowed. The controlled vocabulary needs to be stated in $2 and this is where the information in this list comes from. The mapping tools can not currently handle other language codes, and so cannot FOLIO. <br/>One solution is to migrate the codes into a string or/and replace the language code with the parent language (these languages are usually subgroups of other languages). Many times, these records already have the \"super-language\" recorded as well. The data will remain in the MARC records.",
+  "blurbs.LanguageCodeSources.title": "Language code sources in 041",
+  "blurbs.LanguagesInRecords.description": "A breakdown of language codes occuring in the records. Purely informational.",
+  "blurbs.LanguagesInRecords.title": "Language codes in records",
+  "blurbs.Leader06.description": "",
+  "blurbs.Leader06.title": "Leader 06 (Holdings type)",
+  "blurbs.LeaderManipulation.description": "",
+  "blurbs.LeaderManipulation.title": "Leader manipulation",
+  "blurbs.LegacyLocationCodes.description": "",
+  "blurbs.LegacyLocationCodes.title": "Legacy location codes",
+  "blurbs.LocationMapping.description": "These are the results for the mapping between legacy locations and your new FOLIO location structure",
+  "blurbs.LocationMapping.title": "Location mapping",
+  "blurbs.Locations.description": "",
+  "blurbs.Locations.title": "Locations - Unmapped legacy codes",
+  "blurbs.MalformedInterfaceUri.description": "Interfaces with malformed URIs will not be migrated. See data issues log. FOLIO Interface URIs must start with one of the following: 'ftp://', 'sftp://', 'http://', 'https://'.",
+  "blurbs.MalformedInterfaceUri.title": "Malformed interface URIs",
+  "blurbs.MappedAlternativeTitleTypes.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Alternative title type values. The library should review the total number for each value against what they would expect to see mapped. The FOLIO community recommends a coarse-grained mapping.",
+  "blurbs.MappedAlternativeTitleTypes.title": "Mapped Alternative title types",
+  "blurbs.MappedClassificationTypes.description": "",
+  "blurbs.MappedClassificationTypes.title": "Mapped classification types",
+  "blurbs.MappedContributorNameTypes.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Name type values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MappedContributorNameTypes.title": "Mapped contributor name types",
+  "blurbs.MappedElectronicRelationshipTypes.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Electronic access relationship type values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MappedElectronicRelationshipTypes.title": "Mapped electronic access relationships types",
+  "blurbs.MappedFolioFields.description": "Library action: **REVIEW** <br/>This table shows how many of the FOLIO instance records created contain data in the different FOLIO fields. The library should review the mapped totals against what they would expect to see mapped.",
+  "blurbs.MappedFolioFields.title": "Mapped FOLIO fields",
+  "blurbs.MappedIdentifierTypes.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Identifier type values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MappedIdentifierTypes.title": "Mapped identifier types",
+  "blurbs.MappedInstanceFormats.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Instance format values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MappedInstanceFormats.title": "Mapped instance formats",
+  "blurbs.MappedLegacyFields.description": "Library action: **REVIEW** <br/>This table lists all the MARC fields in the source data, and whether it has been mapped to a FOLIO instance record field. The library should examine the MARC tags with a high 'Unmapped' figure and determine if these MARC tags contain data that you would like mapped to the FOLIO instance record.",
+  "blurbs.MappedLegacyFields.title": "Mapped Legacy Fields",
+  "blurbs.MappedLocations.description": "",
+  "blurbs.MappedLocations.title": "Mapped Locations",
+  "blurbs.MappedNoteTypes.description": "Library action: **REVIEW** <br/>The created FOLIO records contain the following Note type values.  <br/>The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MappedNoteTypes.title": "Mapped note types",
+  "blurbs.MappedPublisherRoleFromIndicator2.description": "Publication Role, taken from the code in Ind2",
+  "blurbs.MappedPublisherRoleFromIndicator2.title": "Mapped publisher role from Indicator2",
+  "blurbs.MarcValidation.description": "",
+  "blurbs.MarcValidation.title": "MARC21 validation issues found in records",
+  "blurbs.MatchedModesOfIssuanceCode.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Mode of issuace values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.MatchedModesOfIssuanceCode.title": "Matched Modes of issuance code",
+  "blurbs.MaterialTypeMapping.description": "",
+  "blurbs.MaterialTypeMapping.title": "Mapped Material Types",
+  "blurbs.MissingInstanceTypeIds.description": "**IC ACTION REQUIRED** These reords should get an instance type ID mapped from 336, or a default of Undefined, or they will not be transformed.",
+  "blurbs.MissingInstanceTypeIds.title": "Records without Instance Type Ids",
+  "blurbs.MissingRequiredProperties.description": "",
+  "blurbs.MissingRequiredProperties.title": "Missing or empty required properties",
+  "blurbs.MissingTitles.description": "**FIX IN SOURCE DATA** These records are missing a 245 field. FOLIO requires an instance title. The library must enter this information for the record to be migrated.",
+  "blurbs.MissingTitles.title": "Records without titles",
+  "blurbs.NonNumericTagsInRecord.description": "Library action: **REVIEW** <br/>Non-numeric tags may indicate locally defined fields.",
+  "blurbs.NonNumericTagsInRecord.title": "Non-numeric tags in records",
+  "blurbs.OrderLineLocationMapping.description": "This is the location for for the purchase order line.",
+  "blurbs.OrderLineLocationMapping.title": "POL location mapping",
+  "blurbs.OrganizationTypeMapping.description": "Reference data mapping for Organization types.",
+  "blurbs.OrganizationTypeMapping.title": "Organization types",
+  "blurbs.POLCreation.description": "Report on how PO-Line creation went, what merge criterias were used, etc",
+  "blurbs.POLCreation.title": "PO-Line creation",
+  "blurbs.PermanentLoanTypeMapping.description": "",
+  "blurbs.PermanentLoanTypeMapping.title": "Permanent Loan type mapping",
+  "blurbs.PossibleCleaningTasks.description": "",
+  "blurbs.PossibleCleaningTasks.title": "Possible cleaning tasks",
+  "blurbs.PrecedingSuccedingTitles.description": "",
+  "blurbs.PrecedingSuccedingTitles.title": "Preceding and Succeeding titles",
+  "blurbs.PurchaseOrderInstanceLinking.description": "Purchase Order Lines can but do not have to be linked to instances",
+  "blurbs.PurchaseOrderInstanceLinking.title": "Linked Instances",
+  "blurbs.PurchaseOrderVendorLinking.description": "All purchase orders MUST be linked to an organization.",
+  "blurbs.PurchaseOrderVendorLinking.title": "Linked Organizations",
+  "blurbs.ReceiptStatusMapping.description": "Mapped reciept status from 008[06]. (LoC documentation)[https://www.loc.gov/marc/holdings/hd008.html]",
+  "blurbs.ReceiptStatusMapping.title": "Reciept status mapping",
+  "blurbs.RecordStatus.description": "Library action: **All values that are not a, c, d, n or p will be set to c. If this is not what you want, you need to correct these values in your system. **<br/>An overview of the Record statuses (Leader position 5) present in your source data.    Pay attention to the number of occurrences of the value 'd'. These d's are expressing that they are deleted, and the records might not work as expected in FOLIO. Consider marking them as suppressed in your current system and export them as a separate batch in order to have them suppressed in FOLIO. Allowed values according to the MARC standard are a,c,d,n,p",
+  "blurbs.RecordStatus.title": "Record status (leader pos 5)",
+  "blurbs.RecourceTypeMapping.description": "Library action: **REVIEW** <br/>The created FOLIO instances contain the following Instance type values. The library should review the total number for each value against what they would expect to see mapped.",
+  "blurbs.RecourceTypeMapping.title": "Resource Type Mapping (336)",
+  "blurbs.ReferenceDataMapping.description": "",
+  "blurbs.ReferenceDataMapping.title": "Reference Data Mapping",
+  "blurbs.Section1.description": "This entries below seem to be related to instances",
+  "blurbs.Section1.title": "__Section 1: instances",
+  "blurbs.Section2.description": "The entries below seem to be related to holdings",
+  "blurbs.Section2.title": "__Section 2: holdings",
+  "blurbs.Section3.description": "The entries below seem to be related to items",
+  "blurbs.Section3.title": "__Section 3: items",
+  "blurbs.StaffOnlyViaIndicator.description": "",
+  "blurbs.StaffOnlyViaIndicator.title": "Set note to staff only via indicator",
+  "blurbs.StatisticalCodeMapping.description": "",
+  "blurbs.StatisticalCodeMapping.title": "Statistical code mapping",
+  "blurbs.StatusMapping.description": "",
+  "blurbs.StatusMapping.title": "Status mapping",
+  "blurbs.Suppression.description": "What records got assigned what suppression setting in the records.",
+  "blurbs.Suppression.title": "Suppression",
+  "blurbs.TemporaryLoanTypeMapping.description": "",
+  "blurbs.TemporaryLoanTypeMapping.title": "Temporary Loan type mapping",
+  "blurbs.TemporaryLocationMapping.description": "",
+  "blurbs.TemporaryLocationMapping.title": "Temporary location mapping",
+  "blurbs.TermsMapping.description": "Reference data mapping for Terms.",
+  "blurbs.TermsMapping.title": "Terms Mapping",
+  "blurbs.Trivia.description": "",
+  "blurbs.Trivia.title": "Trivia",
+  "blurbs.UnmappedContributorNameTypes.description": "**REVIEW/IC ACTION REQUIRED** <br/>Contributor name types present in the source data, but not mapped to a FOLIO value. The library and IC should review values and mapping.",
+  "blurbs.UnmappedContributorNameTypes.title": "Unmapped contributor name types",
+  "blurbs.UnmappedProperties.description": "",
+  "blurbs.UnmappedProperties.title": "Unmapped properties",
+  "blurbs.UnrecognizedLanguageCodes.description": "Library action: **REVIEW** <br/>Language code values in the source data that do not match standard language codes. If not fixed before migration, these will display as Undetermined in the instance record and Filtering by language in Inventory will not be conclusive.",
+  "blurbs.UnrecognizedLanguageCodes.title": "Unrecognized language codes in records",
+  "blurbs.UserGroupMapping.description": "",
+  "blurbs.UserGroupMapping.title": "User group mapping",
+  "blurbs.UsersPerPatronType.description": "",
+  "blurbs.UsersPerPatronType.title": "Users per patron type",
+  "blurbs.ValueErrors.description": "**FIX BEFORE MIGRATION** This section identifies records that have unexpected or missing values that prevent the transformation. The type of error will be specified. The library must resolve the issue for the record to be migrated.",
+  "blurbs.ValueErrors.title": "Records failed to migrate due to Value errors found in Transformation",
+  "blurbs.ValueSetInMappingFile.description": "The value for these fields are set in the mapping file instead of coming from the legacy system data.",
+  "blurbs.ValueSetInMappingFile.title": "Value set in mapping file",
+  "blurbs.ValuesMappedFromLegacyFields.description": "A list fo the values and what they were mapped to",
+  "blurbs.ValuesMappedFromLegacyFields.title": "Values mapped from legacy fields",
+  "created": "created",
+  "instance type code (%{code}) not found in FOLIO": "instance type code (%{code}) not found in FOLIO",
+  "item barcode": "item barcode",
+  "legacy id from %{fro}": "legacy id from %{fro}",
+  "naturalId mapped from %{fro}": "naturalId mapped from %{fro}",
+  "no matching identifier_types in %{names}": "no matching identifier_types in %{names}"
+}
\ No newline at end of file


Test patch:
diff --git a/conftest.py b/conftest.py
index 90098e85..50682fc7 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,6 +1,20 @@
+from pathlib import Path
+import pytest
+
+
 def pytest_addoption(parser):
     parser.addoption("--password", action="store")
     parser.addoption("--tenant_id", action="store")
     parser.addoption("--okapi_url", action="store")
     parser.addoption("--username", action="store")
     # parser.addoption("--folio_release", action="store")
+
+
+i18n_config_file = Path(__file__).parent / "i18n_config.py"
+
+
+@pytest.fixture(scope="session", autouse=True)
+def install_l10n():
+    import i18n
+
+    i18n.load_config(i18n_config_file)
diff --git a/tests/test_basic.py b/tests/test_basic.py
index 6083110f..c9bc0a64 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -2,6 +2,7 @@
 import ast
 import json
 import os
+import i18n
 
 import pymarc
 from folio_uuid import FOLIONamespaces
@@ -16,7 +17,6 @@
 from folio_migration_tools.marc_rules_transformation.rules_mapper_base import (
     RulesMapperBase,
 )
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 # flake8: noqa: E501
@@ -50,9 +50,8 @@ def test_dedupe():
     assert len(rec["identifiers"]) == 3
 
 
-def test_blurbs():
-    b = Blurbs.Introduction
-    assert b[0] == "Introduction"
+def test_blurb_internationalization():
+    assert i18n.t("blurbs.Introduction.title") == "Introduction"
 
 
 def test_get_marc_record():
diff --git a/tests/test_bibs_transformer.py b/tests/test_bibs_transformer.py
index 1fb95cab..ef9f132e 100644
--- a/tests/test_bibs_transformer.py
+++ b/tests/test_bibs_transformer.py
@@ -7,7 +7,6 @@
 )
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.bibs_transformer import BibsTransformer
-from folio_migration_tools.report_blurbs import Blurbs
 
 
 def test_get_object_type():
@@ -28,6 +27,6 @@ def test_set_leader():
         assert record.leader[9] == "a"
         assert record.leader[10] == "2"
         assert record.leader[11] == "2"
-        vals = migration_report.report[Blurbs.LeaderManipulation[0]].items()
+        vals = migration_report.report["LeaderManipulation"].items()
         # Should be 4?
         assert len(vals) == 5
diff --git a/tests/test_legacy_loan.py b/tests/test_legacy_loan.py
index 270f4833..0d2fc1d0 100644
--- a/tests/test_legacy_loan.py
+++ b/tests/test_legacy_loan.py
@@ -1,7 +1,6 @@
 from zoneinfo import ZoneInfo
 
 from folio_migration_tools.migration_report import MigrationReport
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.transaction_migration.legacy_loan import LegacyLoan
 
 
@@ -44,12 +43,12 @@ def test_init_tz():
     assert legacy_loan.renewal_count > 0
     assert (
         "Provided due_date is not UTC, setting tzinfo to tenant timezone (America/Chicago)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
     assert (
         "Provided out_date is not UTC, setting tzinfo to tenant timezone (America/Chicago)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
 
@@ -70,16 +69,16 @@ def test_init_tz_2():
     assert legacy_loan.renewal_count > 0
     assert (
         "Hour and minute not specified for due date. Assuming end of local calendar day (23:59)..."
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
     assert (
         "Provided out_date is not UTC, setting tzinfo to tenant timezone (UTC)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
     assert (
         "Provided due_date is not UTC, setting tzinfo to tenant timezone (UTC)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
 
@@ -102,11 +101,11 @@ def test_init_tz_3():
     assert legacy_loan.renewal_count > 0
     assert (
         "Provided due_date is not UTC, setting tzinfo to tenant timezone (Australia/Sydney)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
     assert (
         "Provided out_date is not UTC, setting tzinfo to tenant timezone (Australia/Sydney)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
 
@@ -129,11 +128,11 @@ def test_init_tz_4():  # Test dates with(out) DST
     assert legacy_loan.renewal_count > 0
     assert (
         "Provided due_date is not UTC, setting tzinfo to tenant timezone (Australia/Sydney)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
     assert (
         "Provided out_date is not UTC, setting tzinfo to tenant timezone (Australia/Sydney)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
 
 
@@ -156,9 +155,9 @@ def test_init_tz_5():  # Test dates with(out) DST
     assert legacy_loan.renewal_count > 0
     assert (
         "Provided due_date is not UTC, setting tzinfo to tenant timezone (America/Chicago)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
     assert (
         "Provided out_date is not UTC, setting tzinfo to tenant timezone (America/Chicago)"
-        in migration_report.report[Blurbs.Details[0]]
+        in migration_report.report["Details"]
     )
diff --git a/tests/test_rules_mapper_bibs.py b/tests/test_rules_mapper_bibs.py
index d87d071b..e0672a52 100644
--- a/tests/test_rules_mapper_bibs.py
+++ b/tests/test_rules_mapper_bibs.py
@@ -25,7 +25,6 @@
 )
 from folio_migration_tools.migration_report import MigrationReport
 from folio_migration_tools.migration_tasks.bibs_transformer import BibsTransformer
-from folio_migration_tools.report_blurbs import Blurbs
 from folio_migration_tools.test_infrastructure import mocked_classes
 
 xpath_245 = "//marc:datafield[@tag='245']"
diff --git a/tests/test_rules_mapper_bibs_no_folio.py b/tests/test_rules_mapper_bibs_no_folio.py
index 8b88b851..5b564957 100644
--- a/tests/test_rules_mapper_bibs_no_folio.py
+++ b/tests/test_rules_mapper_bibs_no_folio.py
@@ -4,14 +4,13 @@
 
 import pymarc
 import pytest
+import i18n
 from dateutil.parser import parse
 from pymarc import Field
 from pymarc import MARCReader
 from pymarc import Record
 from pymarc import Subfield
 
-from folio_migration_tools.report_blurbs import Blurbs
-
 LOGGER = logging.getLogger(__name__)
 LOGGER.propagate = True
 
@@ -66,11 +65,8 @@ def test_handle_suppression_set_false(mapper):
     mapper.handle_suppression(folio_instance, file_def)
     assert folio_instance.get("staffSuppress") is False
     assert folio_instance.get("discoverySuppress") is False
-    assert (
-        mapper.migration_report.report[Blurbs.Suppression[0]]["Suppressed from discovery = False"]
-        == 1
-    )
-    assert mapper.migration_report.report[Blurbs.Suppression[0]]["Staff suppressed = False "] == 1
+    assert mapper.migration_report.report["Suppression"]["Suppressed from discovery = False"] == 1
+    assert mapper.migration_report.report["Suppression"]["Staff suppressed = False "] == 1
 
 
 def test_handle_suppression_set_true(mapper):
@@ -79,11 +75,8 @@ def test_handle_suppression_set_true(mapper):
     mapper.handle_suppression(folio_instance, file_def)
     assert folio_instance.get("staffSuppress") is True
     assert folio_instance.get("discoverySuppress") is True
-    assert (
-        mapper.migration_report.report[Blurbs.Suppression[0]]["Suppressed from discovery = True"]
-        == 1
-    )
-    assert mapper.migration_report.report[Blurbs.Suppression[0]]["Staff suppressed = True "] == 1
+    assert mapper.migration_report.report["Suppression"]["Suppressed from discovery = True"] == 1
+    assert mapper.migration_report.report["Suppression"]["Staff suppressed = True "] == 1
 
 
 def test_get_folio_id_by_code_except(mapper, caplog):
@@ -448,8 +441,8 @@ def test_get_folio_id_by_name(mapper, caplog):
     res = mapper.get_instance_format_id_by_name("audio", "audio belt", "legacy_id_99")
     assert not caplog.text
     assert (
-        "Successful matching on 337$a & 338$a - audio -- audio belt->audio -- audio belt"
-        in mapper.migration_report.report[Blurbs.InstanceFormat[0]]
+        "Successful matching on 337$a and 338$a - audio -- audio belt->audio -- audio belt"
+        in mapper.migration_report.report["InstanceFormat"]
     )
     assert res == "0d9b1c3d-2d13-4f18-9472-cc1b91bf1752"
 
@@ -470,10 +463,8 @@ def test_handle_leader_05(mapper, caplog):
         assert record.leader[5] == "s"
         BibsRulesMapper.handle_leader_05(mapper, record, ["legacy id"])
         assert record.leader[5] == "c"
-        assert (
-            "Original value: s" in mapper.migration_report.report["Record status (leader pos 5)"]
-        )
-        assert "Changed s to c" in mapper.migration_report.report["Record status (leader pos 5)"]
+        assert "Original value: s" in mapper.migration_report.report["RecordStatus"]
+        assert "Changed s to c" in mapper.migration_report.report["RecordStatus"]
 
 
 def test_fieldReplacementBy3Digits(mapper: BibsRulesMapper, caplog):
@@ -493,7 +484,7 @@ def test_fieldReplacementBy3Digits(mapper: BibsRulesMapper, caplog):
         assert "[東京宝塚劇場公演パンフレット. ]" in res["alternativeTitles"][0]["alternativeTitle"]
         assert "1. 7月星組公演.  淀君, シャンソン・ダムール (1959)" in res["notes"][2]["note"]
         assert "宝塚" in res["publication"][1]["place"]
-        assert "Records without $6" in mapper.migration_report.report["880 mappings"]
+        assert "Records without $6" in mapper.migration_report.report["Field880Mappings"]
 
 
 def test_parse_cataloged_date():