From 10868508d03ee78b1f91ce11dcabd464d498e1c1 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 18 Jan 2024 11:45:10 -0500
Subject: [PATCH 01/97] Set develop version to 4.17-dev
---
geniza/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geniza/__init__.py b/geniza/__init__.py
index 940dae072..23fdeebb2 100644
--- a/geniza/__init__.py
+++ b/geniza/__init__.py
@@ -1,4 +1,4 @@
-__version_info__ = (4, 16, 0, None)
+__version_info__ = (4, 17, 0, "dev")
# Dot-connect all but the last. Last is dash-connected if not None.
From 53eaa7ea46f0c1d057f60d6cd9d6cc811acd0ef3 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 18 Jan 2024 12:59:25 -0500
Subject: [PATCH 02/97] Remove one-time-use management command
---
.../management/commands/add_cat_numbers.py | 44 -------------------
1 file changed, 44 deletions(-)
delete mode 100644 geniza/corpus/management/commands/add_cat_numbers.py
diff --git a/geniza/corpus/management/commands/add_cat_numbers.py b/geniza/corpus/management/commands/add_cat_numbers.py
deleted file mode 100644
index 54a6ec243..000000000
--- a/geniza/corpus/management/commands/add_cat_numbers.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-Script to add catalog numbers to historical shelfmarks for some Bodleian
-records. This is a one-time script and should be removed after the import is
-completed in production.
-
-Intended to be run manually from the shell as follows:
-./manage.py add_cat_numbers historical_shelfmarks.csv
-"""
-
-import csv
-import re
-
-from django.core.management.base import BaseCommand
-
-from geniza.corpus.models import Fragment
-
-
-class Command(BaseCommand):
- """Import catalog numbers into Fragment records in the local database."""
-
- bodl_regex = r"^Bodl\. MS Heb\. (?P[A-Za-z]) (?P\d+),"
-
- def add_arguments(self, parser):
- parser.add_argument("path", help="Path to a CSV file")
-
- def handle(self, *args, **kwargs):
- with open(kwargs.get("path"), newline="") as csvfile:
- reader = csv.DictReader(csvfile)
- for row in reader:
- cat_number = row["catalog no. (Bodl. historical shelfmarks)"]
- if cat_number:
- try:
- frag = Fragment.objects.get(pk=int(row["id"]))
- except Fragment.DoesNotExist:
- print(f"Error: cannot find fragment with id {row['id']}")
- continue
-
- # Bodl. MS heb. b 12/6
- # --> data migration --> Bodl. MS Heb. b 12, f. 6
- # --> this script --> Bodl. MS Heb. b 12 (Cat. 2875), f. 6
- hist_repl = f"Bodl. MS Heb. \g \g (Cat. {cat_number}),"
- hist = re.sub(self.bodl_regex, hist_repl, frag.old_shelfmarks)
- frag.old_shelfmarks = hist
- frag.save()
From 3cf41871f0e79a0f3775d6fc404303fb056ee500 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 19 Jan 2024 11:26:17 -0500
Subject: [PATCH 03/97] Add name field to package.json
---
package-lock.json | 1 +
package.json | 1 +
2 files changed, 2 insertions(+)
diff --git a/package-lock.json b/package-lock.json
index 75d3ba0f8..8ac812865 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
+ "name": "geniza",
"dependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
diff --git a/package.json b/package.json
index 713970f01..7906822db 100644
--- a/package.json
+++ b/package.json
@@ -1,4 +1,5 @@
{
+ "name": "geniza",
"scripts": {
"start": "webpack serve --mode=development",
"build": "webpack --mode=production",
From ffa70b9a777a03bd920ad2c2493ecf00a176c647 Mon Sep 17 00:00:00 2001
From: rlskoeser
Date: Thu, 25 Jan 2024 13:52:38 -0500
Subject: [PATCH 04/97] Remove defunct requires.io badge from readme
---
README.rst | 4 ----
1 file changed, 4 deletions(-)
diff --git a/README.rst b/README.rst
index 855d9608b..09375ea68 100644
--- a/README.rst
+++ b/README.rst
@@ -19,10 +19,6 @@ Python 3.9 / Django 3.2 / Node 16 / Postgresql / Solr 9.2
:target: https://codecov.io/gh/Princeton-CDH/geniza
:alt: Code coverage
-.. image:: https://requires.io/github/Princeton-CDH/geniza/requirements.svg?branch=main
- :target: https://requires.io/github/Princeton-CDH/geniza/requirements/?branch=main
- :alt: Requirements Status
-
.. image:: https://github.com/Princeton-CDH/geniza/workflows/dbdocs/badge.svg
:target: https://dbdocs.io/princetoncdh/geniza
:alt: dbdocs build
From 48fd694fedcd6d967b40b3cac740729539182dd1 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 2 Feb 2024 14:09:39 -0500
Subject: [PATCH 05/97] Remove TEI migration and interim sync transcription
code ref #1179 - post v4.9 cleanup
---
.../commands/sync_transcriptions.py | 393 -------------
.../management/commands/tei_to_annotation.py | 529 ------------------
geniza/corpus/tei_transcriptions.py | 266 ---------
geniza/corpus/tests/test_tei_transcription.py | 150 -----
4 files changed, 1338 deletions(-)
delete mode 100644 geniza/corpus/management/commands/sync_transcriptions.py
delete mode 100644 geniza/corpus/management/commands/tei_to_annotation.py
delete mode 100644 geniza/corpus/tei_transcriptions.py
delete mode 100644 geniza/corpus/tests/test_tei_transcription.py
diff --git a/geniza/corpus/management/commands/sync_transcriptions.py b/geniza/corpus/management/commands/sync_transcriptions.py
deleted file mode 100644
index 7a307a166..000000000
--- a/geniza/corpus/management/commands/sync_transcriptions.py
+++ /dev/null
@@ -1,393 +0,0 @@
-"""
-Script to synchronize transcription content from PGP v3 TEI files
-to an _interim_ html format in the database.
-
-The script checks out and updates the transcription files from the
-git repository, and then loops through all xml files and
-identifies the document and footnote to update, if possible.
-
-"""
-
-import glob
-import os.path
-from collections import defaultdict
-
-from django.conf import settings
-from django.contrib.admin.models import ADDITION, CHANGE, LogEntry
-from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
-from django.core.management.base import BaseCommand, CommandError
-from django.db import models
-from django.urls import reverse
-from eulxml import xmlmap
-from git import Repo
-
-from geniza.common.utils import absolutize_url
-from geniza.corpus.models import Document
-from geniza.corpus.tei_transcriptions import GenizaTei
-from geniza.footnotes.models import Footnote, Source
-
-
-class Command(BaseCommand):
- """Synchronize TEI transcriptions to edition footnote content"""
-
- v_normal = 1 # default verbosity
-
- def add_arguments(self, parser):
- parser.add_argument(
- "-n",
- "--noact",
- action="store_true",
- help="Do not save changes to the database",
- )
- parser.add_argument("files", nargs="*", help="Only sync the specified files.")
-
- # dict of footnotes that have been updated with list of TEI files, to track/prevent
- # TEI files resolving incorrectly to the same edition
- footnotes_updated = defaultdict(list)
-
- # keep track of document ids with multiple digitized editions (likely merged records/joins)
- multiedition_docs = set()
-
- def handle(self, *args, **options):
- # get settings for remote git repository url and local path
- gitrepo_url = settings.TEI_TRANSCRIPTIONS_GITREPO
- gitrepo_path = settings.TEI_TRANSCRIPTIONS_LOCAL_PATH
-
- self.verbosity = options["verbosity"]
- self.noact_mode = options["noact"]
-
- # make sure we have latest tei content from git repository
- self.sync_git(gitrepo_url, gitrepo_path)
-
- if not self.noact_mode:
- # get content type and user for log entries, unless in no-act mode
- self.footnote_contenttype = ContentType.objects.get_for_model(Footnote)
- self.script_user = User.objects.get(username=settings.SCRIPT_USERNAME)
-
- self.stats = defaultdict(int)
- # after creating missing goitein unpublished edition notes, these will not be created again
- self.stats["footnote_created"] = 0
- # duplicates might not always happen
- self.stats["duplicate_footnote"] = 0
- # updates should not happen after initial sync when there are no TEI changes
- self.stats["footnote_updated"] = 0
- # empty tei may not happen when running on a subset
- self.stats["empty_tei"] = 0
- self.stats["document_not_found"] = 0
- self.stats["joins"] = 0
- self.stats["no_edition"] = 0
- self.stats["one_edition"] = 0
- self.stats["multiple_editions_with_content"] = 0
- # keep track of document ids with multiple digitized editions (likely merged records/joins)
- self.multiedition_docs = set()
-
- # iterate through all tei files in the repository OR specified files
- xmlfiles = options["files"] or glob.iglob(os.path.join(gitrepo_path, "*.xml"))
- for xmlfile in xmlfiles:
- self.stats["xml"] += 1
- xmlfile_basename = os.path.basename(xmlfile)
-
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- # some files are stubs with no content
- # check if the tei is ok to proceed; (e.g., empty or only translation content)
- # if empty, report and skip
- if not self.check_tei(tei, xmlfile):
- continue
-
- # get the document for the file based on id / old id
- doc = self.get_pgp_document(xmlfile_basename)
- # if document was not found, skip
- if not doc:
- continue
-
- if doc.fragments.count() > 1:
- self.stats["joins"] += 1
-
- footnote = self.get_edition_footnote(doc, tei, xmlfile)
- # if we identified an appropriate footnote, update it
- if footnote:
- # if this footnote has already been chosen in the current script run, don't update again
- if self.footnotes_updated[footnote.pk]:
- self.stderr.write(
- "Footnote %s (PGPID %s) already updated with %s; not overwriting with %s"
- % (
- footnote.pk,
- doc.pk,
- ";".join(self.footnotes_updated[footnote.pk]),
- xmlfile,
- )
- )
- self.stats["duplicate_footnote"] += 1
- else:
- self.footnotes_updated[footnote.pk].append(xmlfile)
-
- # convert into html, return in a list of blocks per inferred page/image
- html_pages = tei.text_to_html()
- text = tei.text_to_plaintext()
-
- # if no html was generated, stop processing
- if not html_pages:
- if self.verbosity >= self.v_normal:
- self.stderr.write("No html generated for %s" % doc.id)
- continue
-
- html = {}
- # assign each page of html to a canvas based on sequence,
- # skipping any non-document images
- for i, image in enumerate(doc.iiif_images(filter_side=True)):
- # stop iterating through images when we run out of pages
- if not html_pages:
- break
- # pop the first page of html off the list
- # and assign to the image canvas uri
- html[image["canvas"]] = html_pages.pop(0)
-
- # if there are any html pages left
- # (either document does not have any iiif images, or not all images)
- # generate local canvas uris and attach transcription content
- if html_pages:
- # document manifest url is /documents/pgpid/iiif/manifest/
- # create canvas uris parallel to that
- canvas_base_uri = "%siiif/canvas/" % doc.permalink
- # iterate through any remaining pages and assign to local canvas uris
- for i, html_chunk in enumerate(html_pages):
- canvas_uri = "%s%d/" % (canvas_base_uri, i)
- html[canvas_uri] = html_chunk
-
- footnote.content = {"html": html, "text": text}
- if footnote.has_changed("content"):
- # don't actually save in --noact mode
- if not self.noact_mode:
- footnote.save()
- # create a log entry to document the change
- self.log_footnote_update(
- footnote, os.path.basename(xmlfile)
- )
-
- # count as a change whether in no-act mode or not
- self.stats["footnote_updated"] += 1
-
- # NOTE: in *one* case there is a TEI file with translation content and
- # no transcription; will get reported as empty, but that's ok — it's out of scope
- # for this script and should be handled elsewhere.
-
- # report on what was done
- # include total number of transcription files,
- # documents with transcriptions, number of fragments, and how how many joins
- self.stats["multi_edition_docs"] = len(self.multiedition_docs)
- self.stdout.write(
- """Processed {xml:,} TEI/XML files; skipped {empty_tei:,} TEI files with no transcription content.
-{document_not_found:,} documents not found in database.
-{joins:,} documents with multiple fragments.
-{multiple_editions:,} documents with multiple editions; {multiple_editions_with_content} multiple editions with content ({multi_edition_docs} unique documents).
-{no_edition:,} documents with no edition.
-{one_edition:,} documents with one edition.
-Updated {footnote_updated:,} footnotes (created {footnote_created:,}; skipped overwriting {duplicate_footnote}).
-""".format(
- **self.stats
- )
- )
-
- for footnote_id, xmlfiles in self.footnotes_updated.items():
- if len(xmlfiles) > 1:
- self.stderr.write(
- "Footnote pk %s updated more than once: %s"
- % (footnote_id, ";".join(xmlfiles))
- )
-
- def check_tei(self, tei, xmlfile):
- """Check TEI and report if it is empty, labels only, or has no content.
-
- :param tei: xmlmap tei instance to check; :class:`~geniza.corpus.tei_transcriptions.GenizaTei`
- :param xmlfile: xml filename, for reporting
- :returns: True if check passes; False if the TEI should be skipped.
- :rtype: bool
- """
- # some files are stubs with no content
- # check if there is no text content; report and return true or false
- if tei.no_content():
- if self.verbosity >= self.v_normal:
- self.stdout.write("%s has no text content, skipping" % xmlfile)
- self.stats["empty_tei"] += 1
- return False
- elif tei.labels_only():
- if self.verbosity >= self.v_normal:
- self.stdout.write(
- "%s has labels only, no other text content; skipping" % xmlfile
- )
- self.stats["empty_tei"] += 1
- return False
- elif not tei.text.lines:
- self.stdout.write("%s has no lines (translation?), skipping" % xmlfile)
- self.stats["empty_tei"] += 1
- return False
-
- return True
-
- def get_pgp_document(self, xmlfile_basename):
- """Find the PGP document for the specified TEI file, based on filename,
- if possible.
-
- :returns: instance of :class:`~geniza.corpus.models.Document` or None if not found
- """
-
- # get the document id from the filename (####.xml)
- pgpid = os.path.splitext(xmlfile_basename)[0]
- # in ONE case there is a duplicate id with b suffix on the second
- try:
- pgpid = int(pgpid.strip("b"))
- except ValueError:
- if self.verbosity >= self.v_normal:
- self.stderr.write("Failed to generate integer PGPID from %s" % pgpid)
- return
- # can we rely on pgpid from xml?
- # but in some cases, it looks like a join 12047 + 12351
-
- # find the document in the database
- try:
- Document.objects.get_by_any_pgpid(pgpid)
- except Document.DoesNotExist:
- self.stats["document_not_found"] += 1
- if self.verbosity >= self.v_normal:
- self.stdout.write("Document %s not found in database" % pgpid)
- return
-
- def get_footnote_editions(self, doc):
- """Get all edition footnotes of a document; used by :meth:`get_edition_footnote`,
- extend to include digital editions in tei to annotation script."""
- return doc.footnotes.editions()
-
- def get_edition_footnote(self, doc, tei, filename):
- """identify the edition footnote to be updated"""
- # get editions for this document
- editions = self.get_footnote_editions(doc)
-
- if editions.count() > 1:
- self.stats["multiple_editions"] += 1
-
- # when there are multiple, try to identify correct edition by author names
- footnote = self.choose_edition_by_authors(tei, editions, doc)
- # if we got a match, use it
- if footnote:
- return footnote
-
- # if not, limit to editions with content and try again
- editions_with_content = editions.filter(content__isnull=False)
- footnote = self.choose_edition_by_authors(tei, editions_with_content, doc)
- if footnote:
- return footnote
-
- # if not, fallback to first edition
- if editions_with_content.count() == 1:
- self.stats["multiple_editions_with_content"] += 1
- self.multiedition_docs.add(doc.id)
-
- # if there was only one, assume it's the one to update
- # NOTE: this is potentially wrong!
- return editions_with_content.first()
-
- if not editions.exists():
- # no editions found; check if we can create a goitein unpublished edition footnote
- footnote = self.is_it_goitein(tei, doc)
- if footnote:
- return footnote
-
- self.stats["no_edition"] += 1
- if self.verbosity > self.v_normal:
- self.stdout.write("No edition found for %s" % filename)
- for line in tei.source:
- self.stdout.write("\t%s" % line)
- else:
- self.stats["one_edition"] += 1
- # if only one edition, update the transciption content there
- return editions.first()
-
- def choose_edition_by_authors(self, tei, editions, doc):
- """Try to choose correct edition from a list based on author names;
- based on structured author names in the TEI"""
- if tei.source_authors:
- tei_authors = set(tei.source_authors)
- author_matches = []
- for ed in editions:
- ed_authors = set([auth.last_name for auth in ed.source.authors.all()])
- if ed_authors == tei_authors:
- author_matches.append(ed)
-
- # if we got exactly one match, use that edition
- if len(author_matches) == 1:
- return author_matches[0]
-
- # if there were *no* author matches, see if we can create a goitein unpublished edition note
- if not author_matches:
- return self.is_it_goitein(tei, doc)
-
- def is_it_goitein(self, tei, doc):
- """Check if a TEI document is a Goitein edition. If no edition exists
- and we can identify based on the TEI as a Goitein unpublished edition,
- then create a new footnote."""
- source_info = str(tei.source[0]).lower()
- if "goitein" in source_info and (
- "unpublished editions" in source_info or "typed texts" in source_info
- ):
- if not self.noact_mode:
- footnote = self.create_goitein_footnote(doc)
- if footnote:
- self.stats["footnote_created"] += 1
- return footnote
-
- def create_goitein_footnote(self, doc):
- """Create a new footnote for a Goitein unpublished edition"""
- source = Source.objects.filter(
- authors__last_name="Goitein",
- title_en="unpublished editions",
- source_type__type="Unpublished",
- volume__startswith=Source.get_volume_from_shelfmark(doc.shelfmark),
- ).first()
- if not source:
- self.stderr.write(
- "Error finding Goitein unpublished editions source for %s"
- % doc.shelfmark
- )
- return
-
- footnote = Footnote.objects.create(
- source=source,
- content_object=doc,
- doc_relation=Footnote.EDITION,
- )
- LogEntry.objects.log_action(
- user_id=self.script_user.id,
- content_type_id=self.footnote_contenttype.pk,
- object_id=footnote.pk,
- object_repr=str(footnote),
- change_message="Created Goitein unpublished editions footnote to sync transcription",
- action_flag=ADDITION,
- )
-
- return footnote
-
- def sync_git(self, gitrepo_url, local_path):
- """ensure git repository has been cloned and content is up to date"""
-
- # if directory does not yet exist, clone repository
- if not os.path.isdir(local_path):
- if self.verbosity >= self.v_normal:
- self.stdout.write(
- "Cloning TEI transcriptions repository to %s" % local_path
- )
- Repo.clone_from(url=gitrepo_url, to_path=local_path)
- else:
- # pull any changes since the last run
- Repo(local_path).remotes.origin.pull()
-
- def log_footnote_update(self, footnote, xmlfile):
- """create a log entry for a footnote that has been updated"""
- LogEntry.objects.log_action(
- user_id=self.script_user.id,
- content_type_id=self.footnote_contenttype.pk,
- object_id=footnote.pk,
- object_repr=str(footnote),
- change_message="Updated transcription from TEI file %s" % xmlfile,
- action_flag=CHANGE,
- )
diff --git a/geniza/corpus/management/commands/tei_to_annotation.py b/geniza/corpus/management/commands/tei_to_annotation.py
deleted file mode 100644
index 8827da3e4..000000000
--- a/geniza/corpus/management/commands/tei_to_annotation.py
+++ /dev/null
@@ -1,529 +0,0 @@
-"""
-Script to convert transcription content from PGP v3 TEI files
-to IIIF annotations in the configured annotation server.
-
-"""
-
-import glob
-import os.path
-import unicodedata
-from collections import defaultdict
-from datetime import datetime
-from functools import cached_property
-
-import requests
-from addict import Dict
-from django.conf import settings
-from django.contrib.admin.models import ADDITION, CHANGE, DELETION, LogEntry
-from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
-from django.core.management.base import BaseCommand, CommandError
-from django.db import models
-from django.template.defaultfilters import pluralize
-from django.urls import reverse
-from django.utils import timezone
-from eulxml import xmlmap
-from git import Repo
-from parasolr.django.signals import IndexableSignalHandler
-from rich.progress import MofNCompleteColumn, Progress
-
-from geniza.annotations.models import Annotation
-from geniza.common.utils import absolutize_url
-from geniza.corpus.annotation_export import AnnotationExporter
-from geniza.corpus.management.commands import sync_transcriptions
-from geniza.corpus.models import Document
-from geniza.corpus.tei_transcriptions import GenizaTei
-from geniza.footnotes.models import Footnote
-
-
-class Command(sync_transcriptions.Command):
- """Synchronize TEI transcriptions to edition footnote content"""
-
- v_normal = 1 # default verbosity
-
- missing_footnotes = []
-
- normalized_unicode = set()
-
- document_not_found = []
-
- def add_arguments(self, parser):
- parser.add_argument(
- "files", nargs="*", help="Only convert the specified files."
- )
-
- def handle(self, *args, **options):
- # get settings for remote git repository url and local path
- gitrepo_url = settings.TEI_TRANSCRIPTIONS_GITREPO
- gitrepo_path = settings.TEI_TRANSCRIPTIONS_LOCAL_PATH
-
- self.verbosity = options["verbosity"]
- # get content type and script user for log entries
- self.script_user = User.objects.get(username=settings.SCRIPT_USERNAME)
-
- # disconnect solr indexing signals
- IndexableSignalHandler.disconnect()
-
- # make sure we have latest tei content from git repository
- # (inherited from sync transcriptions command)
- self.sync_git(gitrepo_url, gitrepo_path)
- # initialize local git repo client
- self.tei_gitrepo = Repo(gitrepo_path)
-
- self.stats = defaultdict(int)
-
- xmlfiles = options["files"] or glob.glob(os.path.join(gitrepo_path, "*.xml"))
- script_run_start = timezone.now()
-
- self.stdout.write("Migrating %d TEI files" % len(xmlfiles))
-
- # when running on all files (i.e., specific files not specified),
- # clear all annotations from the database before running the migration
- # NOTE: could make this optional behavior, but it probably only
- # impacts development and not the real migration?
- if not options["files"]:
- # cheating a little here, but much faster to clear all at once
- # instead of searching and deleting one at a time
- all_annos = Annotation.objects.all()
- self.stdout.write("Clearing %d annotations" % all_annos.count())
- all_annos.delete()
-
- # initialize annotation exporter; don't push changes until the end;
- # commit message will be overridden per export to docment TEI file
- self.anno_exporter = AnnotationExporter(
- stdout=self.stdout,
- verbosity=options["verbosity"],
- push_changes=False,
- commit_msg="PGP transcription export from TEI migration",
- )
- self.anno_exporter.setup_repo()
-
- # use rich progressbar without context manager
- progress = Progress(
- MofNCompleteColumn(), *Progress.get_default_columns(), expand=True
- )
- progress.start()
- task = progress.add_task("Migrating...", total=len(xmlfiles))
-
- # iterate through tei files to be migrated
- for xmlfile in xmlfiles:
- self.stats["xml"] += 1
- # update progress at the beginning instead of end,
- # since some records are skipped
- progress.update(task, advance=1, update=True)
-
- if self.verbosity >= self.v_normal:
- self.stdout.write(xmlfile)
-
- xmlfile_basename = os.path.basename(xmlfile)
-
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- # some files are stubs with no content
- # check if the tei is ok to proceed; (e.g., empty or only translation content)
- # if empty, report and skip
- if not self.check_tei(tei, xmlfile):
- self.stdout.write(
- self.style.WARNING(
- "No transcription content in %s; skipping" % xmlfile
- )
- )
- continue
- # get the document for the file based on id / old id
- doc = self.get_pgp_document(xmlfile_basename)
- # if document was not found, skip
- if not doc:
- self.stdout.write(
- self.style.WARNING("Document not found for %s; skipping" % xmlfile)
- )
- self.document_not_found.append(xmlfile)
- continue
- # found the document
- if self.verbosity >= self.v_normal:
- self.stdout.write(str(doc))
-
- # get the footnote for this file & doc
- footnote = self.get_edition_footnote(doc, tei, xmlfile)
- # if no footnote, skip for now
- # (some are missing, but will handle with data work)
- if not footnote:
- self.stdout.write(
- self.style.ERROR(
- "footnote not found for %s / %s; skipping" % (xmlfile, doc.pk)
- )
- )
- self.missing_footnotes.append(xmlfile)
- continue
- footnote = self.migrate_footnote(footnote, doc)
-
- # if there is a single primary language, use the iso code if it is set
- lang_code = None
- if doc.languages.count() == 1:
- lang_code = doc.languages.first().iso_code
-
- # get html blocks from the tei
- html_blocks = tei.text_to_html(block_format=True)
-
- # get canvas objects for the images in order; skip any non-document images
- iiif_canvases = list(doc.iiif_images(filter_side=True).keys())
- # determine the number of canvases needed based on labels
- # that indicate new pages
- # check and count any after the first; always need at least 1 canvas
- num_canvases = 1 + len(
- [
- b["label"]
- for b in html_blocks[1:]
- if tei.label_indicates_new_page(b["label"])
- ]
- )
- # in verbose mode report on available/needed canvases
- if self.verbosity > self.v_normal:
- self.stdout.write(
- "%d iiif canvases; need %d canvases for %d blocks"
- % (len(iiif_canvases), num_canvases, len(html_blocks))
- )
- # if we need more canvases than we have available,
- # generate local canvas ids
- if num_canvases > len(iiif_canvases):
- # document manifest url is /documents/pgpid/iiif/manifest/
- # create canvas uris parallel to that
- canvas_base_uri = doc.manifest_uri.replace("manifest", "canvas")
- for i in range(num_canvases - len(iiif_canvases)):
- canvas_uri = "%s%d/" % (canvas_base_uri, i + 1)
- iiif_canvases.append(canvas_uri)
-
- # NOTE: pgpid 1390 folio example; each chunk should be half of the canvas
- # (probably should be handled manually)
- # if len(html_chunks) > len(iiif_canvases):
- # self.stdout.write(
- # "%s has more html chunks than canvases; skipping" % xmlfile
- # )
- # continue
-
- # start attaching to first canvas; increment based on chunk label
- canvas_index = 0
-
- # if specific files were specified, remove annotations
- # just for those documents & sources
- if options["files"]:
- # remove all existing annotations associated with this
- # document and source so we can reimport as needed
- existing_annos = Annotation.objects.filter(
- footnote__source=footnote.source,
- footnote__content_object=doc,
- created__lt=script_run_start,
- )
- # NOTE: this is problematic for transcriptions currently
- # split across two TEI files... take care when running
- # on individual or groups of files
- if existing_annos:
- print(
- "Removing %s pre-existing annotation%s for %s on %s "
- % (
- len(existing_annos),
- pluralize(existing_annos),
- footnote.source,
- doc.manifest_uri,
- )
- )
- # not creating log entries for deletion, but
- # this should probably only come up in dev runs
- existing_annos.delete()
-
- for i, block in enumerate(html_blocks):
- # if this is not the first block and the label suggests new image,
- # increment canvas index
- if i != 0 and tei.label_indicates_new_page(block["label"]):
- canvas_index += 1
-
- anno = new_transcription_annotation()
- # get the canvas uri for this section of text
- annotation_target = iiif_canvases[canvas_index]
- anno.target.source.id = annotation_target
-
- # apply to the full canvas using % notation
- # (using nearly full canvas to make it easier to edit zones)
- anno.target.selector.value = "xywh=percent:1,1,98,98"
- # anno.selector.value = "%s#xywh=pixel:0,0,%s,%s" % (annotation_target, canvas.width, canvas.height)
-
- # add html and optional label to annotation text body
- # NOTE: not specifying language in html here because we
- # handle it in wrapping template code based on db language
-
- html = tei.lines_to_html(block["lines"])
- if not unicodedata.is_normalized("NFC", html):
- self.normalized_unicode.add(xmlfile)
- html = unicodedata.normalize("NFC", html)
- anno.body[0].value = html
-
- if block["label"]:
- # check if label text requires normalization
- if not unicodedata.is_normalized("NFC", block["label"]):
- self.normalized_unicode.add(xmlfile)
- block["label"] = unicodedata.normalize("NFC", block["label"])
- anno.body[0].label = block["label"]
-
- anno["schema:position"] = i + 1
- # print(anno) # can print for debugging
-
- # create database annotation
- db_anno = Annotation()
- db_anno.set_content(dict(anno))
- # link to digital edition footnote
- db_anno.footnote = footnote
- db_anno.save()
- # log entry to document annotation creation
- self.log_addition(db_anno, "Migrated from TEI transcription")
- self.stats["created"] += 1
-
- # export migrated transcription to backup
- self.export_transcription(doc, xmlfile_basename)
-
- progress.refresh()
- progress.stop()
-
- print(
- "Processed %(xml)d TEI file(s). \nCreated %(created)d annotation(s)."
- % self.stats
- )
-
- # push all changes from migration to github
- self.anno_exporter.sync_github()
-
- # report on missing footnotes
- if self.missing_footnotes:
- print(
- "Could not find footnotes for %s document%s:"
- % (len(self.missing_footnotes), pluralize(self.missing_footnotes))
- )
- for xmlfile in self.missing_footnotes:
- print("\t%s" % xmlfile)
-
- # report on unicode normalization
- if self.normalized_unicode:
- print(
- "Normalized unicode for %s document%s:"
- % (len(self.normalized_unicode), pluralize(self.normalized_unicode))
- )
- for xmlfile in self.normalized_unicode:
- print("\t%s" % xmlfile)
-
- if self.document_not_found:
- print(
- "Document not found for %s TEI file%s:"
- % (len(self.document_not_found), pluralize(self.document_not_found))
- )
- for xmlfile in self.normalized_unicode:
- print("\t%s" % xmlfile)
-
- # report on edition footnotes that still have content
- # (skip when unning on a specified files)
- if not options["files"]:
- self.check_unmigrated_footnotes()
-
- def get_footnote_editions(self, doc):
- # extend to return digital edition or edition
- # (digital edition if from previous run of this script)
- return doc.footnotes.filter(
- models.Q(doc_relation__contains=Footnote.EDITION)
- | models.Q(doc_relation__contains=Footnote.DIGITAL_EDITION)
- )
-
- # we shouldn't be creating new footnotes at this point...
- # override method from sync transcriptions to ensure we don't
- def is_it_goitein(self, tei, doc):
- return None
-
- def migrate_footnote(self, footnote, document):
- # convert existing edition footnote to digital edition
- # OR make a new one if the existing footnote has other information
-
- # convert existing edition footnote to digital edition
- # OR make a new one if the existing footnote has other information
-
- # if footnote is already a digital edition, nothing to be done
- # (already migrated in a previous run)
- if footnote.doc_relation == Footnote.DIGITAL_EDITION:
- return footnote
-
- # check if a digital edition footnote for this document+source exists,
- # so we can avoid creating a duplicate
- diged_footnote = document.footnotes.filter(
- doc_relation=Footnote.DIGITAL_EDITION, source=footnote.source
- ).first()
-
- # if footnote has other types or a url, we should preserve it
- if (
- set(footnote.doc_relation).intersection(
- {Footnote.TRANSLATION, Footnote.DISCUSSION}
- )
- or footnote.url
- or footnote.location
- ):
- # remove interim transcription content
- if footnote.content:
- footnote.content = None
- footnote.save()
-
- # if a digital edition footnote for this document+source exists,
- # use that instead of creating a duplicate
- if diged_footnote:
- return diged_footnote
-
- # otherwise, make a new one
- new_footnote = Footnote(
- doc_relation=Footnote.DIGITAL_EDITION, source=footnote.source
- )
- # trying to set from related object footnote.document errors
- new_footnote.content_object = document
- new_footnote.save()
- # log footnote creation and return
- self.log_addition(
- new_footnote,
- "Created new footnote for migrated digital edition",
- )
- return new_footnote
-
- # when there is no additional information on the footnote
- else:
- # if a digital edition already exists, remove this one
- if diged_footnote:
- footnote.delete()
- # log deletion and and return existing diged
- self.log_deletion(footnote, "Removing redundant edition footnote")
- return diged_footnote
-
- # otherwise, convert edition to digital edition
- footnote.doc_relation = Footnote.DIGITAL_EDITION
- footnote.content = None
- footnote.save()
- # log footnote change and return
- self.log_change(footnote, "Migrated footnote to digital edition")
- return footnote
-
- # lookup to map tei git repo usernames to pgp db username for co-author string
- teicontributor_to_username = {
- "Alan Elbaum": "ae5677",
- # multiple Bens should all map to same user
- "Ben": "benj",
- "Ben Johnston": "benj",
- "benj@princeton.edu": "benj",
- "benjohnsto": "benj",
- # no github account that I can find; just use the name
- "Brendan Goldman": "Brendan Goldman",
- "Jessica Parker": "jp0630",
- "Ksenia Ryzhova": "kryzhova",
- "Rachel Richman": "rrichman",
- "mrustow": "mrustow",
- # multiple RSKs also...
- "Rebecca Sutton Koeser": "rkoeser",
- "rlskoeser": "rkoeser",
- }
-
- @cached_property
- def tei_contrib_users(self):
- # retrieve users from database based on known tei contributor usernames,
- # and return as a dict for lookup by username
- tei_users = User.objects.filter(
- username__in=set(self.teicontributor_to_username.values())
- )
- return {u.username: u for u in tei_users}
-
- def export_transcription(self, document, xmlfile):
- # get contributors and export to git backup
-
- # get the unique list of all contributors to this file
- commits = list(self.tei_gitrepo.iter_commits("master", paths=xmlfile))
- contributors = set([c.author.name for c in commits])
- # convert bitbucket users to unique set of pgp users
- contrib_usernames = set(
- self.teicontributor_to_username[c] for c in contributors
- )
- # now get actual users for those usernames...
- contrib_users = [self.tei_contrib_users.get(u, u) for u in contrib_usernames]
-
- # export transcription for the specified document,
- # documenting the users who modified the TEI file
- self.anno_exporter.export(
- pgpids=[document.pk],
- modifying_users=contrib_users,
- commit_msg="Transcription migrated from TEI %s" % xmlfile,
- )
-
- def log_addition(self, obj, log_message):
- "create a log entry documenting object creation"
- return self.log_entry(obj, log_message, ADDITION)
-
- def log_change(self, obj, log_message):
- "create a log entry documenting object change"
- return self.log_entry(obj, log_message, CHANGE)
-
- def log_deletion(self, obj, log_message):
- "create a log entry documenting object change"
- return self.log_entry(obj, log_message, DELETION)
-
- def check_unmigrated_footnotes(self):
- unmigrated_footnotes = Footnote.objects.filter(
- doc_relation__contains=Footnote.EDITION, content__isnull=False
- )
- if unmigrated_footnotes.exists():
- self.stdout.write(
- "\n%d unmigrated footnote%s"
- % (unmigrated_footnotes.count(), pluralize(unmigrated_footnotes))
- )
- for fn in unmigrated_footnotes:
- # provide admin link to make it easier to investigate
- admin_url = absolutize_url(
- reverse("admin:footnotes_footnote_change", args=(fn.id,))
- )
- print("\t%s\t%s" % (fn, admin_url))
-
- _content_types = {}
-
- def get_content_type(self, obj):
- # lookup and cache content type for model
- model_class = obj.__class__
- if model_class not in self._content_types:
- self._content_types[model_class] = ContentType.objects.get_for_model(
- model_class
- )
- return self._content_types[model_class]
-
- def log_entry(self, obj, log_message, log_action):
- "create a log entry documenting object creation/change/deletion"
- # for this migration, we can assume user is always script user
- content_type = self.get_content_type(obj)
- return LogEntry.objects.log_action(
- user_id=self.script_user.id,
- content_type_id=content_type.pk,
- object_id=obj.pk,
- object_repr=str(obj),
- change_message=log_message,
- action_flag=log_action,
- )
-
-
-def new_transcription_annotation():
- # initialize a new annotation dict object with all the defaults set
-
- anno = Dict()
- setattr(anno, "@context", "http://www.w3.org/ns/anno.jsonld")
- anno.type = "Annotation"
- anno.body = [Dict()]
- anno.body[0].type = "TextualBody"
- # purpose on body is only needed if more than one body
- # (e.g., transcription + tags in the same annotation)
- # anno.body[0].purpose = "transcribing"
- anno.body[0].format = "text/html"
- # explicitly indicate text direction; all transcriptions are RTL
- anno.body[0].TextInput = "rtl"
- # supplement rather than painting over the image
- # multiple motivations are allowed; add transcribing as secondary motivation
- # (could use edm:transcribing from Europeana Data Model, but not sure
- # how to declare edm namespace)
- anno.motivation = ["sc:supplementing", "transcribing"]
-
- anno.target.source.type = "Canvas"
- anno.target.selector.type = "FragmentSelector"
- anno.target.selector.conformsTo = "http://www.w3.org/TR/media-frags/"
-
- return anno
diff --git a/geniza/corpus/tei_transcriptions.py b/geniza/corpus/tei_transcriptions.py
deleted file mode 100644
index ed3bb5d11..000000000
--- a/geniza/corpus/tei_transcriptions.py
+++ /dev/null
@@ -1,266 +0,0 @@
-from eulxml import xmlmap
-from eulxml.xmlmap import teimap
-
-from geniza.common.utils import simplify_quotes
-
-
-class GenizaTeiLine(teimap.TeiLine):
- name = xmlmap.StringField("local-name(.)")
- lang = xmlmap.StringField("@xml:lang|tei:span/@xml:lang")
- number = xmlmap.StringField("@n")
-
-
-class MainText(teimap.TeiDiv):
- lines = xmlmap.NodeListField("tei:l|tei:label", GenizaTeiLine)
-
-
-class GenizaTei(teimap.Tei):
- # extend eulxml TEI to add mappings for the fields we care about
- # NOTE: at least one pgpid is in format ### + ###
- pgpid = xmlmap.IntegerField('tei:teiHeader//tei:idno[@type="PGP"]')
- # normally main text content is under text/body/div; but at least one document has no div
- text = xmlmap.NodeField(
- "tei:text/tei:body/tei:div|tei:text/tei:body[not(tei:div)]", MainText
- )
- lines = xmlmap.NodeListField("tei:text/tei:body/tei:div/tei:l", GenizaTeiLine)
- labels = xmlmap.NodeListField(
- "tei:text/tei:body/tei:div/tei:label", GenizaTeiLine
- ) # not really a line...
- # source description sometimes contains reference to scholarship record
- source = xmlmap.NodeListField(
- "tei:teiHeader//tei:sourceDesc/tei:msDesc/tei:msContents/tei:p", GenizaTeiLine
- )
- # for documents with more than one transcription, authors have been
- # tagged with last name in n attribute to allow identifying/differentiating
- source_authors = xmlmap.StringListField(
- "tei:teiHeader//tei:sourceDesc//tei:author/@n"
- )
-
- def no_content(self):
- return str(self.text).strip() == ""
-
- # text that generally indicates a new page/image, anywhere in the label
- new_page_indicators = [
- "recto",
- "verso",
- "side ii",
- "page b",
- "page 2",
- "page two",
- 'ע"ב', # Hebrew label for page 2
- ]
- # text that indicates a new page/image at the start of the label
- new_page_start_indicators = ["t-s ", "ts ", "ena ", "moss. "]
-
- def label_indicates_new_page(self, label):
- label = simplify_quotes(label.lower())
- return any(
- [side_label in label for side_label in self.new_page_indicators]
- ) or any(
- label.startswith(start_label)
- for start_label in self.new_page_start_indicators
- )
-
- def labels_only(self):
- text_content = str(self.text).strip()
- label_content = " ".join([str(label).strip() for label in self.labels])
- return text_content == label_content
-
- def text_to_html(self, block_format=False):
- # convert the TEI text content to basic HTML
- blocks = []
- lines = []
- label = []
- # because blocks are indicated by labels without containing elements,
- # iterate over all lines and create blocks based on the labels
- for line in self.text.lines:
- if line.name == "label":
- # append current text block if set, and initialize a new one
- if lines:
- blocks.append(
- {
- "label": "\n".join(label),
- "lines": lines,
- # "languages": list(languages),
- }
- )
- label = []
- lines = []
-
- # store the label; sometimes there are two in a row
- label.append(str(line))
-
- elif line.name == "l":
- # use language codes? unreliable in the xml
- # append tuple of line number, text
- # return empty string for line number if no line attribute
- lines.append((line.number or "", str(line)))
-
- # append the last block
- if lines:
- blocks.append(
- {
- "label": "\n".join(label),
- "lines": lines,
- }
- )
-
- # if block format requested, return blocks without further processing
- if block_format:
- return blocks
-
- # otherwise, return chunked HTML
- return self.chunk_html(blocks)
-
- def chunk_html(self, blocks):
- # combine blocks of text into html, chunked into pages to match sides of images
- html = []
- page = []
- for block in blocks:
-
- # if there is a label and it looks like a new side,
- # start a new section
- if block["label"]:
- if self.label_indicates_new_page(block["label"]):
- # if we have any content, close the previous section
- if page:
- # combine all sections in the page and add to the html
- html.append("\n".join(page))
- # then start a new page
- page = []
-
- # start output for the new block
- output = [""]
- # add label if we have one
- if block["label"]:
- output.append(f"
{block['label']}
")
-
- output.append(self.lines_to_html(block["lines"]))
- output.append("")
- page.append("\n".join(output))
-
- # save the last page
- html.append("\n".join(page))
-
- return html
-
- def lines_to_html(self, lines):
- """Convert lines and line numbers from TEI to HTML, accounting
- for unnumbered lines and lines starting with numbers other than 1.
- Converts to ordered lists and paragraphs; ordered lists have
- start attribute when needed.
-
- :params lines: list of tuples of line number, line text
- :returns: string of html content
- """
-
- html_lines = []
- list_num = 1
- in_list = False
- for line_number, line in lines:
- # convertline number to integer for comparison
- if line_number:
- try:
- line_number = int(line_number)
- except ValueError:
- # in at least one instance, line number is a range "16-17"
- # ignore the problem (??)
- if "-" in line_number:
- line_number = int(line_number.split("-")[0])
-
- # if line is empty, skip it
- if not line.strip():
- continue
-
- # if line is unnumberred, output as a paragraph
- if not line_number:
- # if we were in a list, close it
- if in_list:
- html_lines.append("")
- in_list = False
- list_num = 1
- html_lines.append("
%s
" % line)
-
- # if line number is 1, start a new list
- elif line_number == 1:
- # close any preceeding list
- if in_list:
- html_lines.append("")
-
- in_list = True
- list_num = 1
- html_lines.append("")
- html_lines.append("
%s
" % line)
- # if the line number matches expected next value, output as line
- elif line_number == list_num:
- html_lines.append("
%s
" % line)
-
- # if line number does not match expected list number,
- # start a new list with start attribute specified
- else:
- # close existing list if any
- if in_list:
- html_lines.append("")
-
- # start a new list with the specified number IF numeric
- if isinstance(line_number, int):
- list_num = line_number
- in_list = True
- html_lines.append('' % line_number)
- html_lines.append("
%s
" % line)
- else:
- # if not numeric, we can't use as line number or start
- html_lines.append("")
- # add the n to text to preserve the content
- html_lines.append("
%s %s
" % (line_number, line))
-
- # increment expected list number if we're inside a list
- if in_list:
- list_num += 1
-
- # close the last list, if active
- if in_list:
- html_lines.append("")
-
- return "\n".join(html_lines)
-
- rtl_mark = "\u200F"
- ltr_mark = "\u200E"
-
- def text_to_plaintext(self):
- lines = []
- # because blocks are indicated by labels without containing elements,
- # iterate over all lines and create blocks based on the labels
-
- # errors if there are no lines; sync transcription now checks
- # and won't call in that case
- if not self.text.lines:
- return
-
- # determine longest line so we can pad the text
- longest_line = max(len(str(line)) for line in self.text.lines)
- # some files have descriptions that are making lines much too long,
- # so set a limit on line length
- if longest_line > 100:
- longest_line = 100
- for line in self.text.lines:
- if line.name == "label":
- # blank line to indicate breaks between blocks
- lines.append("")
- lines.append("%s%s" % (self.ltr_mark, line))
- elif line.name == "l":
- line_num = line.number or ""
- # combine line text with line number and right justify;
- # right justify line number
- lines.append(
- " ".join(
- [
- self.rtl_mark,
- str(line).rjust(longest_line),
- self.ltr_mark,
- line_num.rjust(3),
- ]
- )
- )
-
- return "\n".join(lines)
diff --git a/geniza/corpus/tests/test_tei_transcription.py b/geniza/corpus/tests/test_tei_transcription.py
deleted file mode 100644
index 8a1b71258..000000000
--- a/geniza/corpus/tests/test_tei_transcription.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import os.path
-
-from eulxml import xmlmap
-
-from geniza.corpus.tei_transcriptions import GenizaTei
-
-fixture_dir = os.path.join(os.path.dirname(__file__), "fixtures")
-
-xmlfile = os.path.join(fixture_dir, "968.xml")
-
-
-def test_fields():
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- assert tei.pgpid == 968
- # should have text, lines, and labels
- assert tei.text
- assert tei.lines
- assert tei.labels
- assert len(tei.labels) == 4
- assert tei.source_authors == ["Gil"]
-
-
-def test_no_content():
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- # this file has text content
- assert not tei.no_content()
-
- # if we delete the lines and labels, it does not
- tei.lines = []
- tei.labels = []
- assert tei.no_content()
-
-
-def test_labels_only():
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- # fixture has both labels and lines
- assert not tei.labels_only()
-
- # delete all the line elemens so only labels are left
- while len(tei.lines):
- del tei.lines[0]
-
- # now labels only is true
- assert tei.labels_only()
-
-
-def test_block_format():
- tei = xmlmap.load_xmlobject_from_file(xmlfile, GenizaTei)
- blocks = tei.text_to_html(block_format=True)
-
- # should be a list of three items (three sets of lines separated by
{# transcription: keywords in context if any, or excerpt #}
- {% with document_highlights=highlighting|dict_item:document.id lang=document.language_code lang_script=document.language_script %}
+ {% with document_highlights=highlighting|dict_item:document.id lang=document.language_code lang_script=document.language_script %}
- {% if document_highlights.transcription or document.transcription %}
-
+ {% if document_highlights.transcription or document.transcription %}
+
- {% if document_highlights.transcription %}
- {% for snippet in document_highlights.transcription %}{{ snippet.strip|safe }}{% endfor %}
- {% elif document.transcription %}
+ {% if document_highlights.transcription %}
+ {% for snippet in document_highlights.transcription %}{{ snippet.strip|safe }}{% endfor %}
+ {% elif document.transcription %}
{# otherwise, display truncated transcription #}
{# NOTE: might be nice to display N lines instead of using truncatechars #}
- {{ document.transcription.0|safe|truncatechars_html:150 }}
- {% endif %}
-
{# list of tuples of (IIIF image, label, rotation) #}
- {% for image in document.iiif_images|slice:":3" %}
- {% with deg=image.2|stringformat:"i" %}
- {% with rotation="rotation:degrees="|add:deg %}
-
-
-
- {% endwith %}
+ {% for image in document.iiif_images|slice:":3" %}
+ {% with deg=image.2|stringformat:"i" %}
+ {% with rotation="rotation:degrees="|add:deg %}
+
+
+
{% endwith %}
- {% endfor %}
-
- {% endif %}
+ {% endwith %}
+ {% endfor %}
+
+ {% endif %}
{# view link #}
{# Translators: screen-reader label for "view document details" link #}
- {% blocktranslate asvar aria_view_details with document_label=document|get_document_label %}View details for {{ document_label }}{% endblocktranslate %}
-
- {% translate 'View document details' %}
-
-
+ {% blocktranslate asvar aria_view_details with document_label=document|get_document_label %}View details for {{ document_label }}{% endblocktranslate %}
+
+ {% translate 'View document details' %}
+
+
{% endspaceless %}
diff --git a/geniza/corpus/views.py b/geniza/corpus/views.py
index 07db19865..f491cfc75 100644
--- a/geniza/corpus/views.py
+++ b/geniza/corpus/views.py
@@ -197,6 +197,8 @@ def get_queryset(self):
fragsize=150,
requireFieldMatch=True,
)
+ # highlight old shelfmark so we can show match in results
+ .highlight("old_shelfmark", requireFieldMatch=True)
.also("score")
) # include relevance score in results
From beec6ced37d49114288fe725edbf3645c7588236 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 15 Mar 2024 10:23:19 -0400
Subject: [PATCH 71/97] Show historical shelfmarks in document detail page
(#1469)
---
.../templates/corpus/document_detail.html | 27 ++++++++++++++-----
geniza/corpus/tests/conftest.py | 1 +
geniza/corpus/tests/test_corpus_templates.py | 6 ++++-
sitemedia/scss/base/_colors.scss | 4 +--
sitemedia/scss/components/_annotation.scss | 2 +-
sitemedia/scss/components/_transcription.scss | 2 +-
sitemedia/scss/pages/_document.scss | 17 ++++++++----
7 files changed, 42 insertions(+), 17 deletions(-)
diff --git a/geniza/corpus/templates/corpus/document_detail.html b/geniza/corpus/templates/corpus/document_detail.html
index a3f7669e4..ba5cd3303 100644
--- a/geniza/corpus/templates/corpus/document_detail.html
+++ b/geniza/corpus/templates/corpus/document_detail.html
@@ -134,13 +134,26 @@
{% translate 'Tags' %}
{% endif %}
-
- {# Translators: Label for date document was first added to the PGP #}
-
{% translate 'Input date' %}
- {# Translators: Date document was first added to the PGP #}
- {% blocktranslate with date=document.log_entries.last.action_time.year %}
- In PGP since {{ date }}
- {% endblocktranslate %}
+
+
+ {% if document.fragment_historical_shelfmarks %}
+ {# Translators: label for historical/old shelfmarks on document fragments #}
+
{% translate 'Historical shelfmarks' %}
+
{{ document.fragment_historical_shelfmarks }}
+ {% endif %}
+ {# Translators: Label for date document was first added to the PGP #}
+
{% translate 'Input date' %}
+
+ {# Translators: Date document was first added to the PGP #}
+ {% blocktranslate with date=document.log_entries.last.action_time.year %}
+ In PGP since {{ date }}
+ {% endblocktranslate %}
+
+
diff --git a/geniza/corpus/tests/conftest.py b/geniza/corpus/tests/conftest.py
index 9aa727ecd..6a1d10beb 100644
--- a/geniza/corpus/tests/conftest.py
+++ b/geniza/corpus/tests/conftest.py
@@ -21,6 +21,7 @@ def make_fragment(manifest=True):
"""A real fragment from CUL, with URLs for testing."""
return Fragment.objects.create(
shelfmark="CUL Add.2586",
+ old_shelfmarks="ULC Add. 2586",
url="https://cudl.lib.cam.ac.uk/view/MS-ADD-02586",
iiif_url="https://cudl.lib.cam.ac.uk/iiif/MS-ADD-02586",
manifest=Manifest.objects.create(
diff --git a/geniza/corpus/tests/test_corpus_templates.py b/geniza/corpus/tests/test_corpus_templates.py
index c868559a1..931fd9bd2 100644
--- a/geniza/corpus/tests/test_corpus_templates.py
+++ b/geniza/corpus/tests/test_corpus_templates.py
@@ -33,9 +33,13 @@ def test_doctype(self, client, document):
def test_first_input(self, client, document):
"""Document detail template should include document first input date"""
response = client.get(document.get_absolute_url())
- # NOTE: No longer using definition list
assertContains(response, "In PGP since 2004")
+ def test_old_shelfmarks(self, client, document):
+ """Document detail template should include historical shelfmarks"""
+ response = client.get(document.get_absolute_url())
+ assertContains(response, "ULC Add. 2586")
+
def test_tags(self, client, document):
"""Document detail template should include all document tags"""
response = client.get(document.get_absolute_url())
diff --git a/sitemedia/scss/base/_colors.scss b/sitemedia/scss/base/_colors.scss
index b246fe055..c9d973f90 100644
--- a/sitemedia/scss/base/_colors.scss
+++ b/sitemedia/scss/base/_colors.scss
@@ -63,7 +63,7 @@ $off-white: #f7f7f7;
// active filter, metadata bullet color, zoom slider control
--filter-active: #7bac7b;
// "in PGP since" text, edition text in ITT panel
- --input-date-text: #595959;
+ --extra-metadata-text: #595959;
// permalink icon
--permalink-icon: #f7f7f7;
--permalink-icon-bg: #3d3d3d;
@@ -131,7 +131,7 @@ $off-white: #f7f7f7;
// active filter, metadata bullet color, zoom slider control
--filter-active: #c37f97;
// "in PGP since" text, edition text in ITT panel
- --input-date-text: #cfcfcf;
+ --extra-metadata-text: #cfcfcf;
// permalink icon
--permalink-icon: #3d3d3d;
--permalink-icon-bg: #f7f7f7;
diff --git a/sitemedia/scss/components/_annotation.scss b/sitemedia/scss/components/_annotation.scss
index 37223bf75..8b138f300 100644
--- a/sitemedia/scss/components/_annotation.scss
+++ b/sitemedia/scss/components/_annotation.scss
@@ -129,7 +129,7 @@ main.annotating {
align-items: center;
margin: 0 0.5rem;
padding-left: 0.5rem;
- border-left: 2px solid var(--input-date-text);
+ border-left: 2px solid var(--extra-metadata-text);
gap: 0.5rem;
label.tahqiq-tool {
display: flex;
diff --git a/sitemedia/scss/components/_transcription.scss b/sitemedia/scss/components/_transcription.scss
index 0fea5eb0f..7a8bc2aaf 100644
--- a/sitemedia/scss/components/_transcription.scss
+++ b/sitemedia/scss/components/_transcription.scss
@@ -969,7 +969,7 @@ $toggle-default-neg: -82px; /* only applies on tablet up */
h2 + span.current-translation {
display: block;
@include typography.meta;
- color: var(--input-date-text);
+ color: var(--extra-metadata-text);
&.multiple {
min-height: calc(1rem * 1.5 * 2);
}
diff --git a/sitemedia/scss/pages/_document.scss b/sitemedia/scss/pages/_document.scss
index dba72c393..b84217d5d 100644
--- a/sitemedia/scss/pages/_document.scss
+++ b/sitemedia/scss/pages/_document.scss
@@ -96,14 +96,21 @@ main.document {
margin-left: 0;
}
}
- // Input date "In PGP since" text
- section.input-date {
+ // Additional metadata section, like input date ("In PGP since") and historical shelfmark
+ section.extra-metadata {
margin: 12px spacing.$spacing-md 0;
@include breakpoints.for-tablet-landscape-up {
margin: 12px 0 0;
}
@include typography.meta;
- color: var(--input-date-text);
+ color: var(--extra-metadata-text);
+ dl {
+ display: grid;
+ grid-template-columns: 3fr 7fr;
+ dt {
+ font-weight: bold;
+ }
+ }
}
// "Description" header and content
section.description {
@@ -198,7 +205,7 @@ html[lang="he"] main.document {
@include typography.body-he;
}
.container dl.metadata-list,
- .container section.input-date,
+ .container section.extra-metadata,
.container dl.metadata-list dt,
.container dl.metadata-list dd,
dl.metadata-list.tertiary dt#permalink {
@@ -219,7 +226,7 @@ html[lang="ar"] main.document {
.container dl.metadata-list,
.container dl.metadata-list dt,
.container dl.metadata-list dd,
- .container section.input-date,
+ .container section.extra-metadata,
dl.metadata-list.tertiary dt#permalink {
@include typography.meta-ar;
}
From 4e771e6a932edacee4a0e6f6da1a821268f42345 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 15 Mar 2024 10:37:26 -0400
Subject: [PATCH 72/97] Fix malformed html in doc detail template
---
geniza/corpus/templates/corpus/document_detail.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geniza/corpus/templates/corpus/document_detail.html b/geniza/corpus/templates/corpus/document_detail.html
index ba5cd3303..229646b8a 100644
--- a/geniza/corpus/templates/corpus/document_detail.html
+++ b/geniza/corpus/templates/corpus/document_detail.html
@@ -142,7 +142,7 @@
{% if document.fragment_historical_shelfmarks %}
{# Translators: label for historical/old shelfmarks on document fragments #}
-
{% translate 'Historical shelfmarks' %}
+
{% translate 'Historical shelfmarks' %}
{{ document.fragment_historical_shelfmarks }}
{% endif %}
{# Translators: Label for date document was first added to the PGP #}
From 574336d7f86e7a5d8a1fd1c8a1183ad8d11b53f6 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 15 Mar 2024 11:04:23 -0400
Subject: [PATCH 73/97] Update messages for translation
---
geniza/locale/ar/LC_MESSAGES/django.po | 362 +++++++++++++++----------
geniza/locale/en/LC_MESSAGES/django.po | 325 +++++++++++++---------
geniza/locale/he/LC_MESSAGES/django.po | 361 ++++++++++++++----------
3 files changed, 631 insertions(+), 417 deletions(-)
diff --git a/geniza/locale/ar/LC_MESSAGES/django.po b/geniza/locale/ar/LC_MESSAGES/django.po
index 5f9bf6b73..35a70da23 100644
--- a/geniza/locale/ar/LC_MESSAGES/django.po
+++ b/geniza/locale/ar/LC_MESSAGES/django.po
@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-21 18:59-0400\n"
+"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
+"POT-Creation-Date: 2024-03-15 11:03-0400\n"
"PO-Revision-Date: 2024-02-12 23:10\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
@@ -10,7 +10,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
+"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"X-Crowdin-Project: princeton-geniza-project\n"
"X-Crowdin-Project-ID: 520356\n"
"X-Crowdin-Language: ar\n"
@@ -18,143 +19,146 @@ msgstr ""
"X-Crowdin-File-ID: 12\n"
#. Translators: placeholder for keyword search input
-#: corpus/forms.py:161
+#: geniza/corpus/forms.py:164
msgid "search by keyword"
msgstr "البحث بواسطة الكلمة المفتاحية"
#. Translators: accessible label for keyword search input
-#: corpus/forms.py:163
+#: geniza/corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr "الكلمة المفتاحية أو العبارة"
#. Translators: label for sort by relevance
-#: corpus/forms.py:172
+#: geniza/corpus/forms.py:175
msgid "Relevance"
msgstr "الصلة"
#. Translators: label for sort in random order
-#: corpus/forms.py:174
+#: geniza/corpus/forms.py:177
msgid "Random"
msgstr "عشوائي"
#. Translators: label for sort by document date (most recent first)
-#: corpus/forms.py:176
+#: geniza/corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr "تاريخ المستند (الأحدث - الأقدم)"
#. Translators: label for sort by document date (oldest first)
-#: corpus/forms.py:178
+#: geniza/corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr "تاريخ المستند (الأقدم - الأحدث)"
#. Translators: label for alphabetical sort by shelfmark
-#: corpus/forms.py:180
+#: geniza/corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr "علامة الرف (أ-ي)"
#. Translators: label for descending sort by number of scholarship records
-#: corpus/forms.py:182
+#: geniza/corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr "ثبت المراجع والمصادر (أكثر - أقل)"
#. Translators: label for ascending sort by number of scholarship records
-#: corpus/forms.py:184
+#: geniza/corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr "ثبت المراجع والمصادر (أقل - أكثر)"
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: corpus/forms.py:186
+#: geniza/corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr "تاريخ الإدخال (الأحدث - الأقدم)"
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: corpus/forms.py:188
+#: geniza/corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr "تاريخ إدخال PGP (الأقدم - الأحدث)"
#. Translators: label for start year when filtering by date range
-#: corpus/forms.py:191 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:194
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr "من عام"
#. Translators: label for end year when filtering by date range
-#: corpus/forms.py:193 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:196
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr "إلى عام"
#. Translators: label for form sort field
-#: corpus/forms.py:201
+#: geniza/corpus/forms.py:204
msgid "Sort by"
msgstr "الترتيب حسب"
-#: corpus/forms.py:208
+#: geniza/corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr "تواريخ المستند (التقويم الميلادي)"
#. Translators: label for document type search form filter
-#: corpus/forms.py:217
+#: geniza/corpus/forms.py:220
msgid "Document Type"
msgstr "نوع المستند"
#. Translators: label for "has image" search form filter
-#: corpus/forms.py:221
+#: geniza/corpus/forms.py:224
msgid "Has Image"
msgstr "لديه صورة"
#. Translators: label for "has transcription" search form filter
-#: corpus/forms.py:225
+#: geniza/corpus/forms.py:228
msgid "Has Transcription"
msgstr "لديه نسخ"
#. Translators: label for "has translation" search form filter
-#: corpus/forms.py:229
+#: geniza/corpus/forms.py:232
msgid "Has Translation"
msgstr "لديه ترجمة"
#. Translators: label for "has discussion" search form filter
-#: corpus/forms.py:233
+#: geniza/corpus/forms.py:236
msgid "Has Discussion"
msgstr "لديه مناقشة"
#. Translators: Default label when document does not have a type
-#: corpus/forms.py:279 corpus/solr_queryset.py:152
-#: corpus/templates/corpus/snippets/document_header.html:17
-#: corpus/templates/corpus/snippets/document_transcription.html:95
+#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
+#: geniza/corpus/templates/corpus/snippets/document_header.html:17
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr "نوع غير معروف"
-#: corpus/forms.py:311
+#: geniza/corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr "ترتيب الصلة غير متوفر بدون مصطلح البحث بواسطة كلمة مفتاحية."
-#: corpus/models.py:911 templates/base.html:21
+#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
msgid "Princeton Geniza Project"
msgstr "Princeton Geniza Project"
#. Translators: attribution for local IIIF manifests
-#: corpus/models.py:913
+#: geniza/corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr "تجميع بواسطة %(pgp)s."
#. Translators: attribution for local IIIF manifests that include transcription
-#: corpus/models.py:916
+#: geniza/corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr "التجميع والنسخ بواسطة %(pgp)s."
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: corpus/models.py:918
+#: geniza/corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr "قد تطبق قيود إضافية."
#. Translators: label for 'home' link in footer navigation
-#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
+#: geniza/corpus/templates/admin/corpus/app_index.html:9
+#: geniza/templates/footer.html:12
msgid "Home"
msgstr "الصفحة الرئيسية"
#. Translators: Other documents on the same fragment/shelfmark
-#: corpus/templates/admin/corpus/document/change_form.html:41
+#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] "مستندات أخرى على هذه العلامة"
@@ -165,8 +169,8 @@ msgstr[4] "مستندات أخرى على هذه العلامات"
msgstr[5] "مستندات أخرى على هذه العلامات"
#. Translators: Editor label
-#: corpus/templates/corpus/document_detail.html:17
-#: corpus/templates/corpus/document_detail.html:50
+#: geniza/corpus/templates/corpus/document_detail.html:17
+#: geniza/corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] "محرر"
@@ -177,22 +181,32 @@ msgstr[4] "المحررون"
msgstr[5] "المحررون"
#. Translators: label for document metadata section (editor, date, input date)
-#: corpus/templates/corpus/document_detail.html:41
+#: geniza/corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr "بيانات التعريف"
-#: corpus/templates/corpus/document_detail.html:45
+#: geniza/corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr "علامة الرف"
#. Translators: label for date of this document, if known
-#: corpus/templates/corpus/document_detail.html:69
-#: corpus/templates/corpus/snippets/document_result.html:22
+#: geniza/corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr "تاريخ الوثيقة"
+#. Translators: Inferred dating label
+#: geniza/corpus/templates/corpus/document_detail.html:80
+msgid "Inferred Date"
+msgid_plural "Inferred Dates"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+msgstr[4] ""
+msgstr[5] ""
+
#. Translators: Primary language label
-#: corpus/templates/corpus/document_detail.html:80
+#: geniza/corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] "اللغة الأساسية"
@@ -203,7 +217,7 @@ msgstr[4] "اللغات الأساسية"
msgstr[5] "اللغات الأساسية"
#. Translators: Secondary language label
-#: corpus/templates/corpus/document_detail.html:93
+#: geniza/corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] "اللغة الثانوية"
@@ -214,57 +228,73 @@ msgstr[4] "اللغات الثانوية"
msgstr[5] "اللغات الثانوية"
#. Translators: label for tags on a document
-#: corpus/templates/corpus/document_detail.html:109
-#: corpus/templates/corpus/snippets/document_result.html:109
+#: geniza/corpus/templates/corpus/document_detail.html:126
+#: geniza/corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr "العلامات"
+#. Translators: label for secondary/historiographical metadata
+#: geniza/corpus/templates/corpus/document_detail.html:140
+msgid "Additional metadata"
+msgstr ""
+
+#. Translators: label for historical/old shelfmarks on document fragments
+#: geniza/corpus/templates/corpus/document_detail.html:145
+msgid "Historical shelfmarks"
+msgstr ""
+
#. Translators: Label for date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:122
+#: geniza/corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "تاريخ الإدخال"
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:124
-#, python-format
-msgid "\n"
-" In PGP since %(date)s\n"
-" "
-msgstr "\n"
+#: geniza/corpus/templates/corpus/document_detail.html:152
+#, fuzzy, python-format
+#| msgid ""
+#| "\n"
+#| " In PGP since %(date)s\n"
+#| " "
+msgid ""
+"\n"
+" In PGP since %(date)s\n"
+" "
+msgstr ""
+"\n"
" في PGP منذ %(date)s\n"
" "
#. Translators: label for document description
-#: corpus/templates/corpus/document_detail.html:132
+#: geniza/corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr "الوصف"
#. Translators: label for permanent link to a document
-#: corpus/templates/corpus/document_detail.html:159
+#: geniza/corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr "رابط دائم"
#. Translators: Search submit button
-#: corpus/templates/corpus/document_list.html:14
+#: geniza/corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr "إرسال البحث"
-#: corpus/templates/corpus/document_list.html:19
-#: corpus/templates/corpus/document_list.html:30
+#: geniza/corpus/templates/corpus/document_list.html:19
+#: geniza/corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr "عوامل التصفية"
#. Translators: label for 'filters' close button for mobile navigation
-#: corpus/templates/corpus/document_list.html:25
+#: geniza/corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr "إغلاق خيارات التصفية"
-#: corpus/templates/corpus/document_list.html:60
+#: geniza/corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr "تطبيق"
#. Translators: number of search results
-#: corpus/templates/corpus/document_list.html:87
+#: geniza/corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -276,65 +306,76 @@ msgstr[4] "%(count_humanized)s نتائج"
msgstr[5] "%(count_humanized)s مجموع النتائج"
#. Translators: screen reader label for pagination navigation displayed after search results
-#: corpus/templates/corpus/document_list.html:115
+#: geniza/corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: corpus/templates/corpus/document_scholarship.html:21
+#: geniza/corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr "الاقتباس المرجعي"
#. Translators: label for included document relations for a single footnote
-#: corpus/templates/corpus/document_scholarship.html:28
+#: geniza/corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr "يشمل"
#. Translators: label for document relations in list of footnotes
-#: corpus/templates/corpus/document_scholarship.html:31
+#: geniza/corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr "لرؤية %(relation)s"
#. Translators: label for document relations for one footnote with no location or URL
-#: corpus/templates/corpus/document_scholarship.html:36
+#: geniza/corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr "يشمل %(relation)s"
#. Translators: Document edit link for admins
-#: corpus/templates/corpus/snippets/document_header.html:9
+#: geniza/corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr "تحرير"
-#: corpus/templates/corpus/snippets/document_image_rights.html:4
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr "بيان أذونات الصورة"
-#: corpus/templates/corpus/snippets/document_image_rights.html:21
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr "لم يتم ملاحظة أي إسناد أو ترخيص."
-#: corpus/templates/corpus/snippets/document_image_rights.html:38
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr "الصفحة الرئيسية Jewish Theological Seminary"
-#: corpus/templates/corpus/snippets/document_image_rights.html:40
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr "شعار Jewish Theological Seminary"
+#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#, fuzzy
+#| msgid "Document Date"
+msgid "Document date"
+msgstr "تاريخ الوثيقة"
+
+#. Translators: label for historical/old shelfmark on document fragments
+#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+msgid "Historical shelfmark"
+msgstr ""
+
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/snippets/document_result.html:31
+#: geniza/corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr "في PGP منذ"
#. Translators: label for unknown date for date added to PGP
-#: corpus/templates/corpus/snippets/document_result.html:33
+#: geniza/corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: corpus/templates/corpus/snippets/document_result.html:74
+#: geniza/corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -346,7 +387,7 @@ msgstr[4] "%(counter)s نسخ"
msgstr[5] "%(counter)s نسخ"
#. Translators: number of translations for this document
-#: corpus/templates/corpus/snippets/document_result.html:84
+#: geniza/corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -358,7 +399,7 @@ msgstr[4] "%(counter)s ترجمات"
msgstr[5] "%(counter)s ترجمات"
#. Translators: number of sources that discuss this document
-#: corpus/templates/corpus/snippets/document_result.html:94
+#: geniza/corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
@@ -369,112 +410,117 @@ msgstr[3] "%(counter)s مناقشات"
msgstr[4] "%(counter)s مناقشات"
msgstr[5] "%(counter)s مناقشات"
-#: corpus/templates/corpus/snippets/document_result.html:102
+#: geniza/corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr "لا توجد ثبت المراجع والمصادر"
-#: corpus/templates/corpus/snippets/document_result.html:116
+#: geniza/corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr "المزيد"
#. Translators: screen-reader label for "view document details" link
-#: corpus/templates/corpus/snippets/document_result.html:136
+#: geniza/corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: corpus/templates/corpus/snippets/document_result.html:138
+#: geniza/corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr "عرض تفاصيل المستند"
#. Translators: accessibility text label for document detail view tabs navigation
-#: corpus/templates/corpus/snippets/document_tabs.html:4
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr "علامات التبويب"
-#: corpus/templates/corpus/snippets/document_tabs.html:7
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr "تفاصيل المستند"
#. Translators: n_records is number of scholarship records
-#: corpus/templates/corpus/snippets/document_tabs.html:10
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr "ثبت المراجع والمصادر (%(n_records)s)"
-#: corpus/templates/corpus/snippets/document_tabs.html:17
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr "المستندات ذات الصلة %(n_reldocs)s)"
-#: corpus/templates/corpus/snippets/document_transcription.html:23
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr "عرض الصور"
-#: corpus/templates/corpus/snippets/document_transcription.html:27
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr "عرض النسخ"
-#: corpus/templates/corpus/snippets/document_transcription.html:31
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr "عرض الترجمة"
-#: corpus/templates/corpus/snippets/document_transcription.html:74
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr "تكبير و تدوير"
-#: corpus/templates/corpus/snippets/document_transcription.html:98
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr "عرض %(related_doc)s"
-#: corpus/templates/corpus/snippets/document_transcription.html:128
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr "النصوص المفرّغة"
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
+#: geniza/footnotes/models.py:495
+msgid "Translation"
+msgstr "الترجمة"
+
#. Translators: label for a link to a resource with no named location
-#: corpus/templates/corpus/snippets/footnote_location.html:9
+#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr "مورد عبر الإنترنت"
#. Translators: screen reader label for pagination navigation
-#: corpus/templates/corpus/snippets/pagination.html:4
+#: geniza/corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:8
+#: geniza/corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr "السابق"
#. Translators: Title for link to page number in search results
-#: corpus/templates/corpus/snippets/pagination.html:22
+#: geniza/corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr "صفحة %(number)s"
#. Translators: Label for "next page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:63
+#: geniza/corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr "التالي"
#. Translators: title of document search page
-#: corpus/views.py:42
+#: geniza/corpus/views.py:49
msgid "Search Documents"
msgstr "البحث في المستندات"
#. Translators: description of document search page, for search engines
-#: corpus/views.py:44
+#: geniza/corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr "البحث في وثائق جنيزا وتصفحها."
#. Translators: title of document scholarship page
-#: corpus/views.py:350
+#: geniza/corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr "منحة في %(doc)s"
-#: corpus/views.py:357
+#: geniza/corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -486,12 +532,12 @@ msgstr[4] "%(count)d سجلات منح دراسية"
msgstr[5] "%(count)d سجلات منح دراسية"
#. Translators: title of related documents page
-#: corpus/views.py:398
+#: geniza/corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr "المستندات ذات الصلة لـ %(doc)s"
-#: corpus/views.py:405
+#: geniza/corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
@@ -502,163 +548,195 @@ msgstr[3] "%(count)d مستندات ذات صلة"
msgstr[4] "%(count)d مستندات ذات صلة"
msgstr[5] "%(count)d مستندات ذات صلة"
+#: geniza/entities/models.py:144
+msgid "Male"
+msgstr ""
+
+#: geniza/entities/models.py:145
+msgid "Female"
+msgstr ""
+
+#: geniza/entities/models.py:146
+#, fuzzy
+#| msgid "Unknown type"
+msgid "Unknown"
+msgstr "نوع غير معروف"
+
+#: geniza/entities/models.py:412
+msgid "Immediate family relations"
+msgstr ""
+
+#: geniza/entities/models.py:413
+msgid "Extended family"
+msgstr ""
+
+#: geniza/entities/models.py:414
+msgid "Relatives by marriage"
+msgstr ""
+
+#: geniza/entities/models.py:415
+msgid "Business and property relationships"
+msgstr ""
+
+#: geniza/entities/models.py:416
+msgid "Ambiguity"
+msgstr ""
+
#. Translators: Placeholder for when a work has no title available
-#: footnotes/models.py:211
+#: geniza/footnotes/models.py:259
msgid "[digital geniza document edition]"
msgstr "[طبعة وثيقة جينيزا رقمية]"
-#: footnotes/models.py:438
+#: geniza/footnotes/models.py:494
msgid "Edition"
msgstr "الطبعة"
-#: footnotes/models.py:439
-msgid "Translation"
-msgstr "الترجمة"
-
-#: footnotes/models.py:440
+#: geniza/footnotes/models.py:496
msgid "Discussion"
msgstr "المناقشة"
-#: footnotes/models.py:441
-msgid "Digital Edition"
-msgstr ""
-
-#: pages/models.py:13
-msgid "Alternative text for visually impaired users to\n"
+#: geniza/pages/models.py:13
+msgid ""
+"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
-msgstr "نص بديل للمستخدمين ضعاف البصر\n"
+msgstr ""
+"نص بديل للمستخدمين ضعاف البصر\n"
"لإيصال الرسالة المقصودة للصورة في هذا السياق بإيجاز."
-#: pages/models.py:44
-msgid "This text will only be read to non-sighted users and should describe the major insights or takeaways from the graphic. Multiple paragraphs are allowed."
-msgstr "هذا النص سوف يقرأ فقط للمستخدمين غير المبصرين ويجب أن يصف الرؤى أو الأفكار الرئيسية من الرسوم. يسمح بفقرات متعددة."
+#: geniza/pages/models.py:44
+msgid ""
+"This text will only be read to non-sighted users and should describe the "
+"major insights or takeaways from the graphic. Multiple paragraphs are "
+"allowed."
+msgstr ""
+"هذا النص سوف يقرأ فقط للمستخدمين غير المبصرين ويجب أن يصف الرؤى أو الأفكار "
+"الرئيسية من الرسوم. يسمح بفقرات متعددة."
#. Translators: title for Not Found (404) error page
-#: templates/404.html:5 templates/404.html:13
+#: geniza/templates/404.html:5 geniza/templates/404.html:13
msgid "Not Found"
msgstr "لم يتم العثور عليه"
#. Translators: description for Not Found (404) error page
-#: templates/404.html:7 templates/404.html:14
+#: geniza/templates/404.html:7 geniza/templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr "عفواً، لقد أصبت فراغ! لم يتم العثور على الصفحة."
#. Translators: title for Internal Server Error (500) error page
-#: templates/500.html:5 templates/500.html:19
+#: geniza/templates/500.html:5 geniza/templates/500.html:19
msgid "Server Error"
msgstr "خطأ في الخادم"
#. Translators: description for Internal Server Error (500) error page
-#: templates/500.html:7 templates/500.html:20
+#: geniza/templates/500.html:7 geniza/templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr "حدث خطأ في وضع هذه الصفحة معا."
-#: templates/admin/base_site.html:4
+#: geniza/templates/admin/base_site.html:4
msgid "Django site admin"
msgstr "مسؤول موقع Django"
-#: templates/base.html:91
+#: geniza/templates/base.html:91
msgid "Skip to main content"
msgstr "تخطي إلى المحتوى الرئيسي"
#. Translators: accessibility text label for footer site navigation
-#: templates/footer.html:4
+#: geniza/templates/footer.html:4
msgid "footer navigation"
msgstr "التنقل في التذييل"
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: templates/footer.html:21
+#: geniza/templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr "الصفحة الرئيسية لـ Princeton Geniza Lab"
-#: templates/footer.html:28 templates/footer.html:42
+#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: templates/footer.html:35
+#: geniza/templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr "الصفحة الرئيسية لـ Princeton Center for Digital Humanities"
-#: templates/footer.html:46
+#: geniza/templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: templates/footer.html:56
+#: geniza/templates/footer.html:56
msgid "Accessibility"
msgstr "إمكانية الوصول"
#. Translators: official name of Princeton University's board of trustees, for footer
-#: templates/footer.html:61
+#: geniza/templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr "The Trustees of Princeton University"
#. Translators: Creative Commons license text, for footer
-#: templates/footer.html:65
+#: geniza/templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr "ترخيص Creative Commons CC-BY"
#. Translators: International Standard Serial Number (ISSN), for footer
-#: templates/footer.html:72
+#: geniza/templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr "ISSN: 2834-4146"
#. Translators: software version, for footer
-#: templates/footer.html:76
+#: geniza/templates/footer.html:76
msgid "software version"
msgstr "إصدار البرنامج"
#. Translators: official name of Princeton University, for footer
-#: templates/footer.html:83
+#: geniza/templates/footer.html:83
msgid "Princeton University homepage"
msgstr "الصفحة الرئيسية لـ Princeton University"
#. Translators: accessibility text label for site navigation
-#: templates/nav.html:4
+#: geniza/templates/nav.html:4
msgid "main navigation"
msgstr "التنقل الرئيسي"
#. Translators: accessibility label for 'home' button in main navigation
-#: templates/nav.html:11
+#: geniza/templates/nav.html:11
msgid "home"
msgstr "الصفحة الرئيسية"
#. Translators: site name label for 'home' button in main navigation
-#: templates/nav.html:13
+#: geniza/templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr "The Princeton Geniza Project"
#. Translators: label for 'menu' open button for mobile navigation
-#: templates/nav.html:24
+#: geniza/templates/nav.html:24
msgid "Open main navigation menu"
msgstr "فتح قائمة التنقل الرئيسية"
#. Translators: label for 'menu' close button for mobile navigation
-#: templates/nav.html:32
+#: geniza/templates/nav.html:32
msgid "Close main navigation menu"
msgstr "إغلاق قائمة التنقل الرئيسية"
#. Translators: label for language choices in navigation
-#: templates/snippets/language_switcher.html:11
+#: geniza/templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr "قراءة هذه الصفحة في %(language_name)s (%(language_code)s)"
#. Translators: label for link to 'Search' page in main navigation
-#: templates/snippets/menu.html:7
+#: geniza/templates/snippets/menu.html:7
msgid "Search"
msgstr "بحث"
#. Translators: label for main menu back button for mobile navigation
-#: templates/snippets/sub_menu.html:8
+#: geniza/templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr "العودة إلى القائمة الرئيسية"
#. Translators: label for dark mode/light mode theme switcher
-#: templates/snippets/theme_toggle.html:5
+#: geniza/templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "تفعيل الوضع المظلم"
-
diff --git a/geniza/locale/en/LC_MESSAGES/django.po b/geniza/locale/en/LC_MESSAGES/django.po
index 5680ca19d..e59bf9f88 100644
--- a/geniza/locale/en/LC_MESSAGES/django.po
+++ b/geniza/locale/en/LC_MESSAGES/django.po
@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: geniza\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-21 18:59-0400\n"
+"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
+"POT-Creation-Date: 2024-03-15 11:03-0400\n"
"PO-Revision-Date: 2022-08-01 14:25\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -18,236 +18,255 @@ msgstr ""
"X-Crowdin-File-ID: 28\n"
#. Translators: placeholder for keyword search input
-#: corpus/forms.py:161
+#: geniza/corpus/forms.py:164
msgid "search by keyword"
msgstr ""
#. Translators: accessible label for keyword search input
-#: corpus/forms.py:163
+#: geniza/corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr ""
#. Translators: label for sort by relevance
-#: corpus/forms.py:172
+#: geniza/corpus/forms.py:175
msgid "Relevance"
msgstr ""
#. Translators: label for sort in random order
-#: corpus/forms.py:174
+#: geniza/corpus/forms.py:177
msgid "Random"
msgstr ""
#. Translators: label for sort by document date (most recent first)
-#: corpus/forms.py:176
+#: geniza/corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr ""
#. Translators: label for sort by document date (oldest first)
-#: corpus/forms.py:178
+#: geniza/corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr ""
#. Translators: label for alphabetical sort by shelfmark
-#: corpus/forms.py:180
+#: geniza/corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr ""
#. Translators: label for descending sort by number of scholarship records
-#: corpus/forms.py:182
+#: geniza/corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr ""
#. Translators: label for ascending sort by number of scholarship records
-#: corpus/forms.py:184
+#: geniza/corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr ""
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: corpus/forms.py:186
+#: geniza/corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr ""
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: corpus/forms.py:188
+#: geniza/corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr ""
#. Translators: label for start year when filtering by date range
-#: corpus/forms.py:191 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:194
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr ""
#. Translators: label for end year when filtering by date range
-#: corpus/forms.py:193 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:196
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr ""
#. Translators: label for form sort field
-#: corpus/forms.py:201
+#: geniza/corpus/forms.py:204
msgid "Sort by"
msgstr ""
-#: corpus/forms.py:208
+#: geniza/corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr ""
#. Translators: label for document type search form filter
-#: corpus/forms.py:217
+#: geniza/corpus/forms.py:220
msgid "Document Type"
msgstr ""
#. Translators: label for "has image" search form filter
-#: corpus/forms.py:221
+#: geniza/corpus/forms.py:224
msgid "Has Image"
msgstr ""
#. Translators: label for "has transcription" search form filter
-#: corpus/forms.py:225
+#: geniza/corpus/forms.py:228
msgid "Has Transcription"
msgstr ""
#. Translators: label for "has translation" search form filter
-#: corpus/forms.py:229
+#: geniza/corpus/forms.py:232
msgid "Has Translation"
msgstr ""
#. Translators: label for "has discussion" search form filter
-#: corpus/forms.py:233
+#: geniza/corpus/forms.py:236
msgid "Has Discussion"
msgstr ""
#. Translators: Default label when document does not have a type
-#: corpus/forms.py:279 corpus/solr_queryset.py:152
-#: corpus/templates/corpus/snippets/document_header.html:17
-#: corpus/templates/corpus/snippets/document_transcription.html:95
+#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
+#: geniza/corpus/templates/corpus/snippets/document_header.html:17
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr ""
-#: corpus/forms.py:311
+#: geniza/corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr ""
-#: corpus/models.py:911 templates/base.html:21
+#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
msgid "Princeton Geniza Project"
msgstr ""
#. Translators: attribution for local IIIF manifests
-#: corpus/models.py:913
+#: geniza/corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr ""
#. Translators: attribution for local IIIF manifests that include transcription
-#: corpus/models.py:916
+#: geniza/corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr ""
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: corpus/models.py:918
+#: geniza/corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr ""
#. Translators: label for 'home' link in footer navigation
-#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
+#: geniza/corpus/templates/admin/corpus/app_index.html:9
+#: geniza/templates/footer.html:12
msgid "Home"
msgstr "Home"
#. Translators: Other documents on the same fragment/shelfmark
-#: corpus/templates/admin/corpus/document/change_form.html:41
+#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] ""
msgstr[1] ""
#. Translators: Editor label
-#: corpus/templates/corpus/document_detail.html:17
-#: corpus/templates/corpus/document_detail.html:50
+#: geniza/corpus/templates/corpus/document_detail.html:17
+#: geniza/corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] ""
msgstr[1] ""
#. Translators: label for document metadata section (editor, date, input date)
-#: corpus/templates/corpus/document_detail.html:41
+#: geniza/corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr ""
-#: corpus/templates/corpus/document_detail.html:45
+#: geniza/corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr ""
#. Translators: label for date of this document, if known
-#: corpus/templates/corpus/document_detail.html:69
-#: corpus/templates/corpus/snippets/document_result.html:22
+#: geniza/corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr ""
+#. Translators: Inferred dating label
+#: geniza/corpus/templates/corpus/document_detail.html:80
+msgid "Inferred Date"
+msgid_plural "Inferred Dates"
+msgstr[0] ""
+msgstr[1] ""
+
#. Translators: Primary language label
-#: corpus/templates/corpus/document_detail.html:80
+#: geniza/corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] ""
msgstr[1] ""
#. Translators: Secondary language label
-#: corpus/templates/corpus/document_detail.html:93
+#: geniza/corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] ""
msgstr[1] ""
#. Translators: label for tags on a document
-#: corpus/templates/corpus/document_detail.html:109
-#: corpus/templates/corpus/snippets/document_result.html:109
+#: geniza/corpus/templates/corpus/document_detail.html:126
+#: geniza/corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr ""
+#. Translators: label for secondary/historiographical metadata
+#: geniza/corpus/templates/corpus/document_detail.html:140
+msgid "Additional metadata"
+msgstr ""
+
+#. Translators: label for historical/old shelfmarks on document fragments
+#: geniza/corpus/templates/corpus/document_detail.html:145
+msgid "Historical shelfmarks"
+msgstr ""
+
#. Translators: Label for date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:122
+#: geniza/corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "Input date"
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:124
+#: geniza/corpus/templates/corpus/document_detail.html:152
#, python-format
msgid ""
"\n"
-" In PGP since %(date)s\n"
-" "
+" In PGP since %(date)s\n"
+" "
msgstr ""
#. Translators: label for document description
-#: corpus/templates/corpus/document_detail.html:132
+#: geniza/corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr ""
#. Translators: label for permanent link to a document
-#: corpus/templates/corpus/document_detail.html:159
+#: geniza/corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr ""
#. Translators: Search submit button
-#: corpus/templates/corpus/document_list.html:14
+#: geniza/corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr ""
-#: corpus/templates/corpus/document_list.html:19
-#: corpus/templates/corpus/document_list.html:30
+#: geniza/corpus/templates/corpus/document_list.html:19
+#: geniza/corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr ""
#. Translators: label for 'filters' close button for mobile navigation
-#: corpus/templates/corpus/document_list.html:25
+#: geniza/corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr ""
-#: corpus/templates/corpus/document_list.html:60
+#: geniza/corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr ""
#. Translators: number of search results
-#: corpus/templates/corpus/document_list.html:87
+#: geniza/corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -255,65 +274,76 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: screen reader label for pagination navigation displayed after search results
-#: corpus/templates/corpus/document_list.html:115
+#: geniza/corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: corpus/templates/corpus/document_scholarship.html:21
+#: geniza/corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr ""
#. Translators: label for included document relations for a single footnote
-#: corpus/templates/corpus/document_scholarship.html:28
+#: geniza/corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr ""
#. Translators: label for document relations in list of footnotes
-#: corpus/templates/corpus/document_scholarship.html:31
+#: geniza/corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr ""
#. Translators: label for document relations for one footnote with no location or URL
-#: corpus/templates/corpus/document_scholarship.html:36
+#: geniza/corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr ""
#. Translators: Document edit link for admins
-#: corpus/templates/corpus/snippets/document_header.html:9
+#: geniza/corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr ""
-#: corpus/templates/corpus/snippets/document_image_rights.html:4
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr ""
-#: corpus/templates/corpus/snippets/document_image_rights.html:21
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr ""
-#: corpus/templates/corpus/snippets/document_image_rights.html:38
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr ""
-#: corpus/templates/corpus/snippets/document_image_rights.html:40
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr ""
+#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#, fuzzy
+#| msgid "Input date"
+msgid "Document date"
+msgstr "Input date"
+
+#. Translators: label for historical/old shelfmark on document fragments
+#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+msgid "Historical shelfmark"
+msgstr ""
+
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/snippets/document_result.html:31
+#: geniza/corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr ""
#. Translators: label for unknown date for date added to PGP
-#: corpus/templates/corpus/snippets/document_result.html:33
+#: geniza/corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: corpus/templates/corpus/snippets/document_result.html:74
+#: geniza/corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -321,7 +351,7 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: number of translations for this document
-#: corpus/templates/corpus/snippets/document_result.html:84
+#: geniza/corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -329,119 +359,124 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: number of sources that discuss this document
-#: corpus/templates/corpus/snippets/document_result.html:94
+#: geniza/corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
msgstr[0] ""
msgstr[1] ""
-#: corpus/templates/corpus/snippets/document_result.html:102
+#: geniza/corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr ""
-#: corpus/templates/corpus/snippets/document_result.html:116
+#: geniza/corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr ""
#. Translators: screen-reader label for "view document details" link
-#: corpus/templates/corpus/snippets/document_result.html:136
+#: geniza/corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: corpus/templates/corpus/snippets/document_result.html:138
+#: geniza/corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr ""
#. Translators: accessibility text label for document detail view tabs navigation
-#: corpus/templates/corpus/snippets/document_tabs.html:4
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr ""
-#: corpus/templates/corpus/snippets/document_tabs.html:7
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr ""
#. Translators: n_records is number of scholarship records
-#: corpus/templates/corpus/snippets/document_tabs.html:10
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr ""
-#: corpus/templates/corpus/snippets/document_tabs.html:17
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:23
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:27
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:31
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:74
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:98
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr ""
-#: corpus/templates/corpus/snippets/document_transcription.html:128
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr ""
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
+#: geniza/footnotes/models.py:495
+msgid "Translation"
+msgstr ""
+
#. Translators: label for a link to a resource with no named location
-#: corpus/templates/corpus/snippets/footnote_location.html:9
+#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr ""
#. Translators: screen reader label for pagination navigation
-#: corpus/templates/corpus/snippets/pagination.html:4
+#: geniza/corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:8
+#: geniza/corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr ""
#. Translators: Title for link to page number in search results
-#: corpus/templates/corpus/snippets/pagination.html:22
+#: geniza/corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr ""
#. Translators: Label for "next page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:63
+#: geniza/corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr ""
#. Translators: title of document search page
-#: corpus/views.py:42
+#: geniza/corpus/views.py:49
msgid "Search Documents"
msgstr "Search Documents"
#. Translators: description of document search page, for search engines
-#: corpus/views.py:44
+#: geniza/corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr ""
#. Translators: title of document scholarship page
-#: corpus/views.py:350
+#: geniza/corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr ""
-#: corpus/views.py:357
+#: geniza/corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -449,46 +484,70 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: title of related documents page
-#: corpus/views.py:398
+#: geniza/corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr ""
-#: corpus/views.py:405
+#: geniza/corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
msgstr[0] ""
msgstr[1] ""
-#. Translators: Placeholder for when a work has no title available
-#: footnotes/models.py:211
-msgid "[digital geniza document edition]"
+#: geniza/entities/models.py:144
+msgid "Male"
msgstr ""
-#: footnotes/models.py:438
-msgid "Edition"
+#: geniza/entities/models.py:145
+msgid "Female"
msgstr ""
-#: footnotes/models.py:439
-msgid "Translation"
+#: geniza/entities/models.py:146
+msgid "Unknown"
msgstr ""
-#: footnotes/models.py:440
-msgid "Discussion"
+#: geniza/entities/models.py:412
+msgid "Immediate family relations"
+msgstr ""
+
+#: geniza/entities/models.py:413
+msgid "Extended family"
+msgstr ""
+
+#: geniza/entities/models.py:414
+msgid "Relatives by marriage"
+msgstr ""
+
+#: geniza/entities/models.py:415
+msgid "Business and property relationships"
+msgstr ""
+
+#: geniza/entities/models.py:416
+msgid "Ambiguity"
msgstr ""
-#: footnotes/models.py:441
-msgid "Digital Edition"
+#. Translators: Placeholder for when a work has no title available
+#: geniza/footnotes/models.py:259
+msgid "[digital geniza document edition]"
+msgstr ""
+
+#: geniza/footnotes/models.py:494
+msgid "Edition"
+msgstr ""
+
+#: geniza/footnotes/models.py:496
+msgid "Discussion"
msgstr ""
-#: pages/models.py:13
+#: geniza/pages/models.py:13
msgid ""
"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
msgstr ""
-#: pages/models.py:44
+#: geniza/pages/models.py:44
msgid ""
"This text will only be read to non-sighted users and should describe the "
"major insights or takeaways from the graphic. Multiple paragraphs are "
@@ -496,130 +555,130 @@ msgid ""
msgstr ""
#. Translators: title for Not Found (404) error page
-#: templates/404.html:5 templates/404.html:13
+#: geniza/templates/404.html:5 geniza/templates/404.html:13
msgid "Not Found"
msgstr ""
#. Translators: description for Not Found (404) error page
-#: templates/404.html:7 templates/404.html:14
+#: geniza/templates/404.html:7 geniza/templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr ""
#. Translators: title for Internal Server Error (500) error page
-#: templates/500.html:5 templates/500.html:19
+#: geniza/templates/500.html:5 geniza/templates/500.html:19
msgid "Server Error"
msgstr ""
#. Translators: description for Internal Server Error (500) error page
-#: templates/500.html:7 templates/500.html:20
+#: geniza/templates/500.html:7 geniza/templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr ""
-#: templates/admin/base_site.html:4
+#: geniza/templates/admin/base_site.html:4
msgid "Django site admin"
msgstr ""
-#: templates/base.html:91
+#: geniza/templates/base.html:91
msgid "Skip to main content"
msgstr ""
#. Translators: accessibility text label for footer site navigation
-#: templates/footer.html:4
+#: geniza/templates/footer.html:4
msgid "footer navigation"
msgstr ""
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: templates/footer.html:21
+#: geniza/templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr ""
-#: templates/footer.html:28 templates/footer.html:42
+#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: templates/footer.html:35
+#: geniza/templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr ""
-#: templates/footer.html:46
+#: geniza/templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: templates/footer.html:56
+#: geniza/templates/footer.html:56
msgid "Accessibility"
msgstr ""
#. Translators: official name of Princeton University's board of trustees, for footer
-#: templates/footer.html:61
+#: geniza/templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr ""
#. Translators: Creative Commons license text, for footer
-#: templates/footer.html:65
+#: geniza/templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr ""
#. Translators: International Standard Serial Number (ISSN), for footer
-#: templates/footer.html:72
+#: geniza/templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr ""
#. Translators: software version, for footer
-#: templates/footer.html:76
+#: geniza/templates/footer.html:76
msgid "software version"
msgstr ""
#. Translators: official name of Princeton University, for footer
-#: templates/footer.html:83
+#: geniza/templates/footer.html:83
msgid "Princeton University homepage"
msgstr ""
#. Translators: accessibility text label for site navigation
-#: templates/nav.html:4
+#: geniza/templates/nav.html:4
msgid "main navigation"
msgstr ""
#. Translators: accessibility label for 'home' button in main navigation
-#: templates/nav.html:11
+#: geniza/templates/nav.html:11
msgid "home"
msgstr ""
#. Translators: site name label for 'home' button in main navigation
-#: templates/nav.html:13
+#: geniza/templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr ""
#. Translators: label for 'menu' open button for mobile navigation
-#: templates/nav.html:24
+#: geniza/templates/nav.html:24
msgid "Open main navigation menu"
msgstr ""
#. Translators: label for 'menu' close button for mobile navigation
-#: templates/nav.html:32
+#: geniza/templates/nav.html:32
msgid "Close main navigation menu"
msgstr ""
#. Translators: label for language choices in navigation
-#: templates/snippets/language_switcher.html:11
+#: geniza/templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr ""
#. Translators: label for link to 'Search' page in main navigation
-#: templates/snippets/menu.html:7
+#: geniza/templates/snippets/menu.html:7
msgid "Search"
msgstr ""
#. Translators: label for main menu back button for mobile navigation
-#: templates/snippets/sub_menu.html:8
+#: geniza/templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr ""
#. Translators: label for dark mode/light mode theme switcher
-#: templates/snippets/theme_toggle.html:5
+#: geniza/templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr ""
diff --git a/geniza/locale/he/LC_MESSAGES/django.po b/geniza/locale/he/LC_MESSAGES/django.po
index a7f196c4c..c11212e63 100644
--- a/geniza/locale/he/LC_MESSAGES/django.po
+++ b/geniza/locale/he/LC_MESSAGES/django.po
@@ -1,8 +1,8 @@
msgid ""
msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-21 18:59-0400\n"
+"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
+"POT-Creation-Date: 2024-03-15 11:03-0400\n"
"PO-Revision-Date: 2024-02-12 23:10\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
@@ -10,7 +10,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
+"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
+"n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: princeton-geniza-project\n"
"X-Crowdin-Project-ID: 520356\n"
"X-Crowdin-Language: he\n"
@@ -18,143 +19,146 @@ msgstr ""
"X-Crowdin-File-ID: 12\n"
#. Translators: placeholder for keyword search input
-#: corpus/forms.py:161
+#: geniza/corpus/forms.py:164
msgid "search by keyword"
msgstr "חיפוש לפי מילת מפתח"
#. Translators: accessible label for keyword search input
-#: corpus/forms.py:163
+#: geniza/corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr "מילת מפתח או ביטוי"
#. Translators: label for sort by relevance
-#: corpus/forms.py:172
+#: geniza/corpus/forms.py:175
msgid "Relevance"
msgstr "רלוונטיות"
#. Translators: label for sort in random order
-#: corpus/forms.py:174
+#: geniza/corpus/forms.py:177
msgid "Random"
msgstr "אקראי"
#. Translators: label for sort by document date (most recent first)
-#: corpus/forms.py:176
+#: geniza/corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr "תאריך המסמך (מאוחר ביותר - מוקדם ביותר)"
#. Translators: label for sort by document date (oldest first)
-#: corpus/forms.py:178
+#: geniza/corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr "תאריך המסמך (מוקדם ביותר - מאוחר ביותר)"
#. Translators: label for alphabetical sort by shelfmark
-#: corpus/forms.py:180
+#: geniza/corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr "מספר מדף (A-Z)"
#. Translators: label for descending sort by number of scholarship records
-#: corpus/forms.py:182
+#: geniza/corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr "רשומות קשורות (מהגדול לקטן)"
#. Translators: label for ascending sort by number of scholarship records
-#: corpus/forms.py:184
+#: geniza/corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr "רשומות קשורות (מהקטן לגדול)"
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: corpus/forms.py:186
+#: geniza/corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr "תאריך הוספה (מאוחר ביותר - מוקדם ביותר)"
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: corpus/forms.py:188
+#: geniza/corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr "תאריך הוספה (מוקדם ביותר - מאוחר ביותר)"
#. Translators: label for start year when filtering by date range
-#: corpus/forms.py:191 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:194
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr "משנת"
#. Translators: label for end year when filtering by date range
-#: corpus/forms.py:193 corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: geniza/corpus/forms.py:196
+#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr "עד שנת"
#. Translators: label for form sort field
-#: corpus/forms.py:201
+#: geniza/corpus/forms.py:204
msgid "Sort by"
msgstr "מיון לפי"
-#: corpus/forms.py:208
+#: geniza/corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr "תאריכי המסמך (CE)"
#. Translators: label for document type search form filter
-#: corpus/forms.py:217
+#: geniza/corpus/forms.py:220
msgid "Document Type"
msgstr "סוג המסמך"
#. Translators: label for "has image" search form filter
-#: corpus/forms.py:221
+#: geniza/corpus/forms.py:224
msgid "Has Image"
msgstr "קיים תצלום"
#. Translators: label for "has transcription" search form filter
-#: corpus/forms.py:225
+#: geniza/corpus/forms.py:228
msgid "Has Transcription"
msgstr "קיים תיעתוק"
#. Translators: label for "has translation" search form filter
-#: corpus/forms.py:229
+#: geniza/corpus/forms.py:232
msgid "Has Translation"
msgstr "קיים תרגום"
#. Translators: label for "has discussion" search form filter
-#: corpus/forms.py:233
+#: geniza/corpus/forms.py:236
msgid "Has Discussion"
msgstr "קיים דיון"
#. Translators: Default label when document does not have a type
-#: corpus/forms.py:279 corpus/solr_queryset.py:152
-#: corpus/templates/corpus/snippets/document_header.html:17
-#: corpus/templates/corpus/snippets/document_transcription.html:95
+#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
+#: geniza/corpus/templates/corpus/snippets/document_header.html:17
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr "סוג לא ידוע"
-#: corpus/forms.py:311
+#: geniza/corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr "מיון על פי רלוונטיות אינו זמין ללא מילת מפתח לחיפוש."
-#: corpus/models.py:911 templates/base.html:21
+#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
msgid "Princeton Geniza Project"
msgstr "Princeton Geniza Project"
#. Translators: attribution for local IIIF manifests
-#: corpus/models.py:913
+#: geniza/corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr "קובץ על-ידי %(pgp)s."
#. Translators: attribution for local IIIF manifests that include transcription
-#: corpus/models.py:916
+#: geniza/corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr "קובץ ותועתק על-ידי %(pgp)s."
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: corpus/models.py:918
+#: geniza/corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr "ייתכנו מגבלות נוספות."
#. Translators: label for 'home' link in footer navigation
-#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
+#: geniza/corpus/templates/admin/corpus/app_index.html:9
+#: geniza/templates/footer.html:12
msgid "Home"
msgstr "דף הבית"
#. Translators: Other documents on the same fragment/shelfmark
-#: corpus/templates/admin/corpus/document/change_form.html:41
+#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] "מסמכים נוספים עם מספר מדף זה"
@@ -163,8 +167,8 @@ msgstr[2] "מסמכים נוספים עם מספרי מדף אלו"
msgstr[3] "מסמכים נוספים עם מספרי מדף אלו"
#. Translators: Editor label
-#: corpus/templates/corpus/document_detail.html:17
-#: corpus/templates/corpus/document_detail.html:50
+#: geniza/corpus/templates/corpus/document_detail.html:17
+#: geniza/corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] "עורך"
@@ -173,22 +177,30 @@ msgstr[2] "עורכים"
msgstr[3] "עורכים"
#. Translators: label for document metadata section (editor, date, input date)
-#: corpus/templates/corpus/document_detail.html:41
+#: geniza/corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr "מטא-דאטא"
-#: corpus/templates/corpus/document_detail.html:45
+#: geniza/corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr "מספר מדף"
#. Translators: label for date of this document, if known
-#: corpus/templates/corpus/document_detail.html:69
-#: corpus/templates/corpus/snippets/document_result.html:22
+#: geniza/corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr "תאריך המסמך"
+#. Translators: Inferred dating label
+#: geniza/corpus/templates/corpus/document_detail.html:80
+msgid "Inferred Date"
+msgid_plural "Inferred Dates"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
#. Translators: Primary language label
-#: corpus/templates/corpus/document_detail.html:80
+#: geniza/corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] "שפה עיקרית"
@@ -197,7 +209,7 @@ msgstr[2] "שפות עיקריות"
msgstr[3] "שפות עיקריות"
#. Translators: Secondary language label
-#: corpus/templates/corpus/document_detail.html:93
+#: geniza/corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] "שפה משנית"
@@ -206,57 +218,73 @@ msgstr[2] "שפות משניות"
msgstr[3] "שפות משניות"
#. Translators: label for tags on a document
-#: corpus/templates/corpus/document_detail.html:109
-#: corpus/templates/corpus/snippets/document_result.html:109
+#: geniza/corpus/templates/corpus/document_detail.html:126
+#: geniza/corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr "תגים"
+#. Translators: label for secondary/historiographical metadata
+#: geniza/corpus/templates/corpus/document_detail.html:140
+msgid "Additional metadata"
+msgstr ""
+
+#. Translators: label for historical/old shelfmarks on document fragments
+#: geniza/corpus/templates/corpus/document_detail.html:145
+msgid "Historical shelfmarks"
+msgstr ""
+
#. Translators: Label for date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:122
+#: geniza/corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "תאריך קלט"
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/document_detail.html:124
-#, python-format
-msgid "\n"
-" In PGP since %(date)s\n"
-" "
-msgstr "\n"
+#: geniza/corpus/templates/corpus/document_detail.html:152
+#, fuzzy, python-format
+#| msgid ""
+#| "\n"
+#| " In PGP since %(date)s\n"
+#| " "
+msgid ""
+"\n"
+" In PGP since %(date)s\n"
+" "
+msgstr ""
+"\n"
"נמצא בPGP מאז %(date)s\n"
" "
#. Translators: label for document description
-#: corpus/templates/corpus/document_detail.html:132
+#: geniza/corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr "תיאור"
#. Translators: label for permanent link to a document
-#: corpus/templates/corpus/document_detail.html:159
+#: geniza/corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr "קישור קבוע"
#. Translators: Search submit button
-#: corpus/templates/corpus/document_list.html:14
+#: geniza/corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr "חיפוש"
-#: corpus/templates/corpus/document_list.html:19
-#: corpus/templates/corpus/document_list.html:30
+#: geniza/corpus/templates/corpus/document_list.html:19
+#: geniza/corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr "מסננים"
#. Translators: label for 'filters' close button for mobile navigation
-#: corpus/templates/corpus/document_list.html:25
+#: geniza/corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr "סגור אפשרויות סינון"
-#: corpus/templates/corpus/document_list.html:60
+#: geniza/corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr "החל"
#. Translators: number of search results
-#: corpus/templates/corpus/document_list.html:87
+#: geniza/corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -266,65 +294,76 @@ msgstr[2] "%(count_humanized)s תוצאות"
msgstr[3] "%(count_humanized)s תוצאות"
#. Translators: screen reader label for pagination navigation displayed after search results
-#: corpus/templates/corpus/document_list.html:115
+#: geniza/corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: corpus/templates/corpus/document_scholarship.html:21
+#: geniza/corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr "ציטוט"
#. Translators: label for included document relations for a single footnote
-#: corpus/templates/corpus/document_scholarship.html:28
+#: geniza/corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr "כלול"
#. Translators: label for document relations in list of footnotes
-#: corpus/templates/corpus/document_scholarship.html:31
+#: geniza/corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr "ל%(relation)s ראה"
#. Translators: label for document relations for one footnote with no location or URL
-#: corpus/templates/corpus/document_scholarship.html:36
+#: geniza/corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr "כולל %(relation)s"
#. Translators: Document edit link for admins
-#: corpus/templates/corpus/snippets/document_header.html:9
+#: geniza/corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr "עריכה"
-#: corpus/templates/corpus/snippets/document_image_rights.html:4
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr "תנאי היתר שימוש בתצלום"
-#: corpus/templates/corpus/snippets/document_image_rights.html:21
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr "לא מצוין ייחוס או רישיון."
-#: corpus/templates/corpus/snippets/document_image_rights.html:38
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr "עמוד הבית Jewish Theological Seminary"
-#: corpus/templates/corpus/snippets/document_image_rights.html:40
+#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr "סמל Jewish Theological Seminary"
+#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#, fuzzy
+#| msgid "Document Date"
+msgid "Document date"
+msgstr "תאריך המסמך"
+
+#. Translators: label for historical/old shelfmark on document fragments
+#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+msgid "Historical shelfmark"
+msgstr ""
+
#. Translators: Date document was first added to the PGP
-#: corpus/templates/corpus/snippets/document_result.html:31
+#: geniza/corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr "נמצא בPGP מאז"
#. Translators: label for unknown date for date added to PGP
-#: corpus/templates/corpus/snippets/document_result.html:33
+#: geniza/corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: corpus/templates/corpus/snippets/document_result.html:74
+#: geniza/corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -334,7 +373,7 @@ msgstr[2] "%(counter)s תעתוקים"
msgstr[3] "%(counter)s תעתוקים"
#. Translators: number of translations for this document
-#: corpus/templates/corpus/snippets/document_result.html:84
+#: geniza/corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -344,7 +383,7 @@ msgstr[2] "%(counter)s תרגומים"
msgstr[3] "%(counter)s תרגומים"
#. Translators: number of sources that discuss this document
-#: corpus/templates/corpus/snippets/document_result.html:94
+#: geniza/corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
@@ -353,112 +392,117 @@ msgstr[1] "%(counter)s דיונים"
msgstr[2] "%(counter)s דיונים"
msgstr[3] "%(counter)s דיונים"
-#: corpus/templates/corpus/snippets/document_result.html:102
+#: geniza/corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr "אין רשומות קשורות"
-#: corpus/templates/corpus/snippets/document_result.html:116
+#: geniza/corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr "עוד"
#. Translators: screen-reader label for "view document details" link
-#: corpus/templates/corpus/snippets/document_result.html:136
+#: geniza/corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: corpus/templates/corpus/snippets/document_result.html:138
+#: geniza/corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr "הצגת פרטי מסמך"
#. Translators: accessibility text label for document detail view tabs navigation
-#: corpus/templates/corpus/snippets/document_tabs.html:4
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr "כרטיסיות"
-#: corpus/templates/corpus/snippets/document_tabs.html:7
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr "פרטי מסמך"
#. Translators: n_records is number of scholarship records
-#: corpus/templates/corpus/snippets/document_tabs.html:10
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr "רשומות קשורות (%(n_records)s)"
-#: corpus/templates/corpus/snippets/document_tabs.html:17
+#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr "מסמכים קשורים (%(n_reldocs)s)"
-#: corpus/templates/corpus/snippets/document_transcription.html:23
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr "הצג תמונות"
-#: corpus/templates/corpus/snippets/document_transcription.html:27
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr "הצג תעתוק"
-#: corpus/templates/corpus/snippets/document_transcription.html:31
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr "הצג תרגום"
-#: corpus/templates/corpus/snippets/document_transcription.html:74
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr "הגדל וסובב"
-#: corpus/templates/corpus/snippets/document_transcription.html:98
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr "ראה %(related_doc)s"
-#: corpus/templates/corpus/snippets/document_transcription.html:128
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr "תיעתוק"
+#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
+#: geniza/footnotes/models.py:495
+msgid "Translation"
+msgstr "תרגום"
+
#. Translators: label for a link to a resource with no named location
-#: corpus/templates/corpus/snippets/footnote_location.html:9
+#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr "מקור אינטרנטי"
#. Translators: screen reader label for pagination navigation
-#: corpus/templates/corpus/snippets/pagination.html:4
+#: geniza/corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:8
+#: geniza/corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr "הקודם"
#. Translators: Title for link to page number in search results
-#: corpus/templates/corpus/snippets/pagination.html:22
+#: geniza/corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr "%(number)s עמודים"
#. Translators: Label for "next page" button in search results
-#: corpus/templates/corpus/snippets/pagination.html:63
+#: geniza/corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr "הבא"
#. Translators: title of document search page
-#: corpus/views.py:42
+#: geniza/corpus/views.py:49
msgid "Search Documents"
msgstr "חיפוש מסמכים"
#. Translators: description of document search page, for search engines
-#: corpus/views.py:44
+#: geniza/corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr "חיפוש וצפיה במסמכי הגניזה."
#. Translators: title of document scholarship page
-#: corpus/views.py:350
+#: geniza/corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr "רשומה קשורה ל-%(doc)s"
-#: corpus/views.py:357
+#: geniza/corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -468,12 +512,12 @@ msgstr[2] "%(count)d רשומות קשורות"
msgstr[3] "%(count)d רשומות קשורות"
#. Translators: title of related documents page
-#: corpus/views.py:398
+#: geniza/corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr "מסמכים קשורים %(doc)s"
-#: corpus/views.py:405
+#: geniza/corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
@@ -482,162 +526,195 @@ msgstr[1] "%(count)d מסמכים קשורים"
msgstr[2] "%(count)d מסמכים קשורים"
msgstr[3] "%(count)d מסמכים קשורים"
+#: geniza/entities/models.py:144
+msgid "Male"
+msgstr ""
+
+#: geniza/entities/models.py:145
+msgid "Female"
+msgstr ""
+
+#: geniza/entities/models.py:146
+#, fuzzy
+#| msgid "Unknown type"
+msgid "Unknown"
+msgstr "סוג לא ידוע"
+
+#: geniza/entities/models.py:412
+msgid "Immediate family relations"
+msgstr ""
+
+#: geniza/entities/models.py:413
+msgid "Extended family"
+msgstr ""
+
+#: geniza/entities/models.py:414
+msgid "Relatives by marriage"
+msgstr ""
+
+#: geniza/entities/models.py:415
+msgid "Business and property relationships"
+msgstr ""
+
+#: geniza/entities/models.py:416
+msgid "Ambiguity"
+msgstr ""
+
#. Translators: Placeholder for when a work has no title available
-#: footnotes/models.py:211
+#: geniza/footnotes/models.py:259
msgid "[digital geniza document edition]"
msgstr "[גרסת מסמך גניזה דיגיטלי]"
-#: footnotes/models.py:438
+#: geniza/footnotes/models.py:494
msgid "Edition"
msgstr "מהדורה"
-#: footnotes/models.py:439
-msgid "Translation"
-msgstr "תרגום"
-
-#: footnotes/models.py:440
+#: geniza/footnotes/models.py:496
msgid "Discussion"
msgstr "דיון"
-#: footnotes/models.py:441
-msgid "Digital Edition"
-msgstr ""
-
-#: pages/models.py:13
-msgid "Alternative text for visually impaired users to\n"
+#: geniza/pages/models.py:13
+msgid ""
+"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
-msgstr "טקסט אלטרנטיבי למשתמשים כבדי ראייה שמטרתו להעביר בתמצית את המסר של התצלום בהקשר זה."
+msgstr ""
+"טקסט אלטרנטיבי למשתמשים כבדי ראייה שמטרתו להעביר בתמצית את המסר של התצלום "
+"בהקשר זה."
-#: pages/models.py:44
-msgid "This text will only be read to non-sighted users and should describe the major insights or takeaways from the graphic. Multiple paragraphs are allowed."
-msgstr "טקסט זה יוקרא למשתמשים עיוורים בלבד ותכליתו לתאר את התובנות והמסרים המרכזיים שעולים מהתוכן הגרפי. ניתן לכלול מספר פסקאות."
+#: geniza/pages/models.py:44
+msgid ""
+"This text will only be read to non-sighted users and should describe the "
+"major insights or takeaways from the graphic. Multiple paragraphs are "
+"allowed."
+msgstr ""
+"טקסט זה יוקרא למשתמשים עיוורים בלבד ותכליתו לתאר את התובנות והמסרים המרכזיים "
+"שעולים מהתוכן הגרפי. ניתן לכלול מספר פסקאות."
#. Translators: title for Not Found (404) error page
-#: templates/404.html:5 templates/404.html:13
+#: geniza/templates/404.html:5 geniza/templates/404.html:13
msgid "Not Found"
msgstr "לא נמצא"
#. Translators: description for Not Found (404) error page
-#: templates/404.html:7 templates/404.html:14
+#: geniza/templates/404.html:7 geniza/templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr "אופס, נתקלת בלאקונה! העמוד המבוקש לא נמצא."
#. Translators: title for Internal Server Error (500) error page
-#: templates/500.html:5 templates/500.html:19
+#: geniza/templates/500.html:5 geniza/templates/500.html:19
msgid "Server Error"
msgstr "שגיאת שרת"
#. Translators: description for Internal Server Error (500) error page
-#: templates/500.html:7 templates/500.html:20
+#: geniza/templates/500.html:7 geniza/templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr "דבר מה השתבש בהפקת עמוד זה."
-#: templates/admin/base_site.html:4
+#: geniza/templates/admin/base_site.html:4
msgid "Django site admin"
msgstr "ניהול האתר"
-#: templates/base.html:91
+#: geniza/templates/base.html:91
msgid "Skip to main content"
msgstr "דילוג לתוכן"
#. Translators: accessibility text label for footer site navigation
-#: templates/footer.html:4
+#: geniza/templates/footer.html:4
msgid "footer navigation"
msgstr "ניווט בתחתית העמוד"
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: templates/footer.html:21
+#: geniza/templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr "עמוד הבית Princeton Geniza Lab"
-#: templates/footer.html:28 templates/footer.html:42
+#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: templates/footer.html:35
+#: geniza/templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr "Princeton Center for Digital Humanities עמוד הבית"
-#: templates/footer.html:46
+#: geniza/templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: templates/footer.html:56
+#: geniza/templates/footer.html:56
msgid "Accessibility"
msgstr "נגישות"
#. Translators: official name of Princeton University's board of trustees, for footer
-#: templates/footer.html:61
+#: geniza/templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr "The Trustees of Princeton University"
#. Translators: Creative Commons license text, for footer
-#: templates/footer.html:65
+#: geniza/templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr "דרישת ייחוס על פי רישיון ״קריאייטיב קומונס״ (CC-BY)"
#. Translators: International Standard Serial Number (ISSN), for footer
-#: templates/footer.html:72
+#: geniza/templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr "ISSN: 2834-4146"
#. Translators: software version, for footer
-#: templates/footer.html:76
+#: geniza/templates/footer.html:76
msgid "software version"
msgstr "גרסת תוכנה"
#. Translators: official name of Princeton University, for footer
-#: templates/footer.html:83
+#: geniza/templates/footer.html:83
msgid "Princeton University homepage"
msgstr "עמוד הבית Princeton University"
#. Translators: accessibility text label for site navigation
-#: templates/nav.html:4
+#: geniza/templates/nav.html:4
msgid "main navigation"
msgstr "ניווט"
#. Translators: accessibility label for 'home' button in main navigation
-#: templates/nav.html:11
+#: geniza/templates/nav.html:11
msgid "home"
msgstr "דף הבית"
#. Translators: site name label for 'home' button in main navigation
-#: templates/nav.html:13
+#: geniza/templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr "The Princeton Geniza Project"
#. Translators: label for 'menu' open button for mobile navigation
-#: templates/nav.html:24
+#: geniza/templates/nav.html:24
msgid "Open main navigation menu"
msgstr "פתיחת תפריט ניווט"
#. Translators: label for 'menu' close button for mobile navigation
-#: templates/nav.html:32
+#: geniza/templates/nav.html:32
msgid "Close main navigation menu"
msgstr "סגירת תפריט ניווט"
#. Translators: label for language choices in navigation
-#: templates/snippets/language_switcher.html:11
+#: geniza/templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr "קריאת העמוד ב-%(language_name)s (%(language_code)s)"
#. Translators: label for link to 'Search' page in main navigation
-#: templates/snippets/menu.html:7
+#: geniza/templates/snippets/menu.html:7
msgid "Search"
msgstr "חיפוש"
#. Translators: label for main menu back button for mobile navigation
-#: templates/snippets/sub_menu.html:8
+#: geniza/templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr "חזרה לתפריט הראשי"
#. Translators: label for dark mode/light mode theme switcher
-#: templates/snippets/theme_toggle.html:5
+#: geniza/templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "הפעלת מצב כהה"
-
From 54a60f368fd861260572a6c6de2a0cf9a4897270 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 15 Mar 2024 12:29:39 -0400
Subject: [PATCH 74/97] Regenerate translation files
---
geniza/locale/ar/LC_MESSAGES/django.po | 275 ++++++++++++-------------
geniza/locale/en/LC_MESSAGES/django.po | 275 ++++++++++++-------------
geniza/locale/he/LC_MESSAGES/django.po | 275 ++++++++++++-------------
3 files changed, 408 insertions(+), 417 deletions(-)
diff --git a/geniza/locale/ar/LC_MESSAGES/django.po b/geniza/locale/ar/LC_MESSAGES/django.po
index 35a70da23..a9d0f1feb 100644
--- a/geniza/locale/ar/LC_MESSAGES/django.po
+++ b/geniza/locale/ar/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
-"POT-Creation-Date: 2024-03-15 11:03-0400\n"
+"POT-Creation-Date: 2024-03-15 12:29-0400\n"
"PO-Revision-Date: 2024-02-12 23:10\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
@@ -19,146 +19,143 @@ msgstr ""
"X-Crowdin-File-ID: 12\n"
#. Translators: placeholder for keyword search input
-#: geniza/corpus/forms.py:164
+#: corpus/forms.py:164
msgid "search by keyword"
msgstr "البحث بواسطة الكلمة المفتاحية"
#. Translators: accessible label for keyword search input
-#: geniza/corpus/forms.py:166
+#: corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr "الكلمة المفتاحية أو العبارة"
#. Translators: label for sort by relevance
-#: geniza/corpus/forms.py:175
+#: corpus/forms.py:175
msgid "Relevance"
msgstr "الصلة"
#. Translators: label for sort in random order
-#: geniza/corpus/forms.py:177
+#: corpus/forms.py:177
msgid "Random"
msgstr "عشوائي"
#. Translators: label for sort by document date (most recent first)
-#: geniza/corpus/forms.py:179
+#: corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr "تاريخ المستند (الأحدث - الأقدم)"
#. Translators: label for sort by document date (oldest first)
-#: geniza/corpus/forms.py:181
+#: corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr "تاريخ المستند (الأقدم - الأحدث)"
#. Translators: label for alphabetical sort by shelfmark
-#: geniza/corpus/forms.py:183
+#: corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr "علامة الرف (أ-ي)"
#. Translators: label for descending sort by number of scholarship records
-#: geniza/corpus/forms.py:185
+#: corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr "ثبت المراجع والمصادر (أكثر - أقل)"
#. Translators: label for ascending sort by number of scholarship records
-#: geniza/corpus/forms.py:187
+#: corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr "ثبت المراجع والمصادر (أقل - أكثر)"
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: geniza/corpus/forms.py:189
+#: corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr "تاريخ الإدخال (الأحدث - الأقدم)"
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: geniza/corpus/forms.py:191
+#: corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr "تاريخ إدخال PGP (الأقدم - الأحدث)"
#. Translators: label for start year when filtering by date range
-#: geniza/corpus/forms.py:194
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:194 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr "من عام"
#. Translators: label for end year when filtering by date range
-#: geniza/corpus/forms.py:196
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:196 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr "إلى عام"
#. Translators: label for form sort field
-#: geniza/corpus/forms.py:204
+#: corpus/forms.py:204
msgid "Sort by"
msgstr "الترتيب حسب"
-#: geniza/corpus/forms.py:211
+#: corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr "تواريخ المستند (التقويم الميلادي)"
#. Translators: label for document type search form filter
-#: geniza/corpus/forms.py:220
+#: corpus/forms.py:220
msgid "Document Type"
msgstr "نوع المستند"
#. Translators: label for "has image" search form filter
-#: geniza/corpus/forms.py:224
+#: corpus/forms.py:224
msgid "Has Image"
msgstr "لديه صورة"
#. Translators: label for "has transcription" search form filter
-#: geniza/corpus/forms.py:228
+#: corpus/forms.py:228
msgid "Has Transcription"
msgstr "لديه نسخ"
#. Translators: label for "has translation" search form filter
-#: geniza/corpus/forms.py:232
+#: corpus/forms.py:232
msgid "Has Translation"
msgstr "لديه ترجمة"
#. Translators: label for "has discussion" search form filter
-#: geniza/corpus/forms.py:236
+#: corpus/forms.py:236
msgid "Has Discussion"
msgstr "لديه مناقشة"
#. Translators: Default label when document does not have a type
-#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
-#: geniza/corpus/templates/corpus/snippets/document_header.html:17
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
+#: corpus/forms.py:282 corpus/solr_queryset.py:240
+#: corpus/templates/corpus/snippets/document_header.html:17
+#: corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr "نوع غير معروف"
-#: geniza/corpus/forms.py:314
+#: corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr "ترتيب الصلة غير متوفر بدون مصطلح البحث بواسطة كلمة مفتاحية."
-#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
+#: corpus/models.py:1081 templates/base.html:21
msgid "Princeton Geniza Project"
msgstr "Princeton Geniza Project"
#. Translators: attribution for local IIIF manifests
-#: geniza/corpus/models.py:1083
+#: corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr "تجميع بواسطة %(pgp)s."
#. Translators: attribution for local IIIF manifests that include transcription
-#: geniza/corpus/models.py:1086
+#: corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr "التجميع والنسخ بواسطة %(pgp)s."
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: geniza/corpus/models.py:1088
+#: corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr "قد تطبق قيود إضافية."
#. Translators: label for 'home' link in footer navigation
-#: geniza/corpus/templates/admin/corpus/app_index.html:9
-#: geniza/templates/footer.html:12
+#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
msgid "Home"
msgstr "الصفحة الرئيسية"
#. Translators: Other documents on the same fragment/shelfmark
-#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
+#: corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] "مستندات أخرى على هذه العلامة"
@@ -169,8 +166,8 @@ msgstr[4] "مستندات أخرى على هذه العلامات"
msgstr[5] "مستندات أخرى على هذه العلامات"
#. Translators: Editor label
-#: geniza/corpus/templates/corpus/document_detail.html:17
-#: geniza/corpus/templates/corpus/document_detail.html:50
+#: corpus/templates/corpus/document_detail.html:17
+#: corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] "محرر"
@@ -181,21 +178,21 @@ msgstr[4] "المحررون"
msgstr[5] "المحررون"
#. Translators: label for document metadata section (editor, date, input date)
-#: geniza/corpus/templates/corpus/document_detail.html:41
+#: corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr "بيانات التعريف"
-#: geniza/corpus/templates/corpus/document_detail.html:45
+#: corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr "علامة الرف"
#. Translators: label for date of this document, if known
-#: geniza/corpus/templates/corpus/document_detail.html:69
+#: corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr "تاريخ الوثيقة"
#. Translators: Inferred dating label
-#: geniza/corpus/templates/corpus/document_detail.html:80
+#: corpus/templates/corpus/document_detail.html:80
msgid "Inferred Date"
msgid_plural "Inferred Dates"
msgstr[0] ""
@@ -206,7 +203,7 @@ msgstr[4] ""
msgstr[5] ""
#. Translators: Primary language label
-#: geniza/corpus/templates/corpus/document_detail.html:97
+#: corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] "اللغة الأساسية"
@@ -217,7 +214,7 @@ msgstr[4] "اللغات الأساسية"
msgstr[5] "اللغات الأساسية"
#. Translators: Secondary language label
-#: geniza/corpus/templates/corpus/document_detail.html:110
+#: corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] "اللغة الثانوية"
@@ -228,28 +225,28 @@ msgstr[4] "اللغات الثانوية"
msgstr[5] "اللغات الثانوية"
#. Translators: label for tags on a document
-#: geniza/corpus/templates/corpus/document_detail.html:126
-#: geniza/corpus/templates/corpus/snippets/document_result.html:137
+#: corpus/templates/corpus/document_detail.html:126
+#: corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr "العلامات"
#. Translators: label for secondary/historiographical metadata
-#: geniza/corpus/templates/corpus/document_detail.html:140
+#: corpus/templates/corpus/document_detail.html:140
msgid "Additional metadata"
msgstr ""
#. Translators: label for historical/old shelfmarks on document fragments
-#: geniza/corpus/templates/corpus/document_detail.html:145
+#: corpus/templates/corpus/document_detail.html:145
msgid "Historical shelfmarks"
msgstr ""
#. Translators: Label for date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:149
+#: corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "تاريخ الإدخال"
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:152
+#: corpus/templates/corpus/document_detail.html:152
#, fuzzy, python-format
#| msgid ""
#| "\n"
@@ -265,36 +262,36 @@ msgstr ""
" "
#. Translators: label for document description
-#: geniza/corpus/templates/corpus/document_detail.html:162
+#: corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr "الوصف"
#. Translators: label for permanent link to a document
-#: geniza/corpus/templates/corpus/document_detail.html:174
+#: corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr "رابط دائم"
#. Translators: Search submit button
-#: geniza/corpus/templates/corpus/document_list.html:14
+#: corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr "إرسال البحث"
-#: geniza/corpus/templates/corpus/document_list.html:19
-#: geniza/corpus/templates/corpus/document_list.html:30
+#: corpus/templates/corpus/document_list.html:19
+#: corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr "عوامل التصفية"
#. Translators: label for 'filters' close button for mobile navigation
-#: geniza/corpus/templates/corpus/document_list.html:25
+#: corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr "إغلاق خيارات التصفية"
-#: geniza/corpus/templates/corpus/document_list.html:60
+#: corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr "تطبيق"
#. Translators: number of search results
-#: geniza/corpus/templates/corpus/document_list.html:87
+#: corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -306,76 +303,76 @@ msgstr[4] "%(count_humanized)s نتائج"
msgstr[5] "%(count_humanized)s مجموع النتائج"
#. Translators: screen reader label for pagination navigation displayed after search results
-#: geniza/corpus/templates/corpus/document_list.html:103
+#: corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: geniza/corpus/templates/corpus/document_scholarship.html:21
+#: corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr "الاقتباس المرجعي"
#. Translators: label for included document relations for a single footnote
-#: geniza/corpus/templates/corpus/document_scholarship.html:28
+#: corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr "يشمل"
#. Translators: label for document relations in list of footnotes
-#: geniza/corpus/templates/corpus/document_scholarship.html:31
+#: corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr "لرؤية %(relation)s"
#. Translators: label for document relations for one footnote with no location or URL
-#: geniza/corpus/templates/corpus/document_scholarship.html:36
+#: corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr "يشمل %(relation)s"
#. Translators: Document edit link for admins
-#: geniza/corpus/templates/corpus/snippets/document_header.html:9
+#: corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr "تحرير"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
+#: corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr "بيان أذونات الصورة"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
+#: corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr "لم يتم ملاحظة أي إسناد أو ترخيص."
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
+#: corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr "الصفحة الرئيسية Jewish Theological Seminary"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
+#: corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr "شعار Jewish Theological Seminary"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#: corpus/templates/corpus/snippets/document_result.html:22
#, fuzzy
#| msgid "Document Date"
msgid "Document date"
msgstr "تاريخ الوثيقة"
#. Translators: label for historical/old shelfmark on document fragments
-#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+#: corpus/templates/corpus/snippets/document_result.html:33
msgid "Historical shelfmark"
msgstr ""
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:38
+#: corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr "في PGP منذ"
#. Translators: label for unknown date for date added to PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:40
+#: corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:102
+#: corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -387,7 +384,7 @@ msgstr[4] "%(counter)s نسخ"
msgstr[5] "%(counter)s نسخ"
#. Translators: number of translations for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:112
+#: corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -399,7 +396,7 @@ msgstr[4] "%(counter)s ترجمات"
msgstr[5] "%(counter)s ترجمات"
#. Translators: number of sources that discuss this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:122
+#: corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
@@ -410,117 +407,117 @@ msgstr[3] "%(counter)s مناقشات"
msgstr[4] "%(counter)s مناقشات"
msgstr[5] "%(counter)s مناقشات"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:130
+#: corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr "لا توجد ثبت المراجع والمصادر"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:144
+#: corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr "المزيد"
#. Translators: screen-reader label for "view document details" link
-#: geniza/corpus/templates/corpus/snippets/document_result.html:168
+#: corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:170
+#: corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr "عرض تفاصيل المستند"
#. Translators: accessibility text label for document detail view tabs navigation
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
+#: corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr "علامات التبويب"
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
+#: corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr "تفاصيل المستند"
#. Translators: n_records is number of scholarship records
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
+#: corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr "ثبت المراجع والمصادر (%(n_records)s)"
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
+#: corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr "المستندات ذات الصلة %(n_reldocs)s)"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
+#: corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr "عرض الصور"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
+#: corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr "عرض النسخ"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
+#: corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr "عرض الترجمة"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
+#: corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr "تكبير و تدوير"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
+#: corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr "عرض %(related_doc)s"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
+#: corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr "النصوص المفرّغة"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
-#: geniza/footnotes/models.py:495
+#: corpus/templates/corpus/snippets/document_transcription.html:239
+#: footnotes/models.py:495
msgid "Translation"
msgstr "الترجمة"
#. Translators: label for a link to a resource with no named location
-#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
+#: corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr "مورد عبر الإنترنت"
#. Translators: screen reader label for pagination navigation
-#: geniza/corpus/templates/corpus/snippets/pagination.html:4
+#: corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:8
+#: corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr "السابق"
#. Translators: Title for link to page number in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:22
+#: corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr "صفحة %(number)s"
#. Translators: Label for "next page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:63
+#: corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr "التالي"
#. Translators: title of document search page
-#: geniza/corpus/views.py:49
+#: corpus/views.py:49
msgid "Search Documents"
msgstr "البحث في المستندات"
#. Translators: description of document search page, for search engines
-#: geniza/corpus/views.py:51
+#: corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr "البحث في وثائق جنيزا وتصفحها."
#. Translators: title of document scholarship page
-#: geniza/corpus/views.py:382
+#: corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr "منحة في %(doc)s"
-#: geniza/corpus/views.py:389
+#: corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -532,12 +529,12 @@ msgstr[4] "%(count)d سجلات منح دراسية"
msgstr[5] "%(count)d سجلات منح دراسية"
#. Translators: title of related documents page
-#: geniza/corpus/views.py:430
+#: corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr "المستندات ذات الصلة لـ %(doc)s"
-#: geniza/corpus/views.py:437
+#: corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
@@ -548,54 +545,54 @@ msgstr[3] "%(count)d مستندات ذات صلة"
msgstr[4] "%(count)d مستندات ذات صلة"
msgstr[5] "%(count)d مستندات ذات صلة"
-#: geniza/entities/models.py:144
+#: entities/models.py:144
msgid "Male"
msgstr ""
-#: geniza/entities/models.py:145
+#: entities/models.py:145
msgid "Female"
msgstr ""
-#: geniza/entities/models.py:146
+#: entities/models.py:146
#, fuzzy
#| msgid "Unknown type"
msgid "Unknown"
msgstr "نوع غير معروف"
-#: geniza/entities/models.py:412
+#: entities/models.py:412
msgid "Immediate family relations"
msgstr ""
-#: geniza/entities/models.py:413
+#: entities/models.py:413
msgid "Extended family"
msgstr ""
-#: geniza/entities/models.py:414
+#: entities/models.py:414
msgid "Relatives by marriage"
msgstr ""
-#: geniza/entities/models.py:415
+#: entities/models.py:415
msgid "Business and property relationships"
msgstr ""
-#: geniza/entities/models.py:416
+#: entities/models.py:416
msgid "Ambiguity"
msgstr ""
#. Translators: Placeholder for when a work has no title available
-#: geniza/footnotes/models.py:259
+#: footnotes/models.py:259
msgid "[digital geniza document edition]"
msgstr "[طبعة وثيقة جينيزا رقمية]"
-#: geniza/footnotes/models.py:494
+#: footnotes/models.py:494
msgid "Edition"
msgstr "الطبعة"
-#: geniza/footnotes/models.py:496
+#: footnotes/models.py:496
msgid "Discussion"
msgstr "المناقشة"
-#: geniza/pages/models.py:13
+#: pages/models.py:13
msgid ""
"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
@@ -603,7 +600,7 @@ msgstr ""
"نص بديل للمستخدمين ضعاف البصر\n"
"لإيصال الرسالة المقصودة للصورة في هذا السياق بإيجاز."
-#: geniza/pages/models.py:44
+#: pages/models.py:44
msgid ""
"This text will only be read to non-sighted users and should describe the "
"major insights or takeaways from the graphic. Multiple paragraphs are "
@@ -613,130 +610,130 @@ msgstr ""
"الرئيسية من الرسوم. يسمح بفقرات متعددة."
#. Translators: title for Not Found (404) error page
-#: geniza/templates/404.html:5 geniza/templates/404.html:13
+#: templates/404.html:5 templates/404.html:13
msgid "Not Found"
msgstr "لم يتم العثور عليه"
#. Translators: description for Not Found (404) error page
-#: geniza/templates/404.html:7 geniza/templates/404.html:14
+#: templates/404.html:7 templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr "عفواً، لقد أصبت فراغ! لم يتم العثور على الصفحة."
#. Translators: title for Internal Server Error (500) error page
-#: geniza/templates/500.html:5 geniza/templates/500.html:19
+#: templates/500.html:5 templates/500.html:19
msgid "Server Error"
msgstr "خطأ في الخادم"
#. Translators: description for Internal Server Error (500) error page
-#: geniza/templates/500.html:7 geniza/templates/500.html:20
+#: templates/500.html:7 templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr "حدث خطأ في وضع هذه الصفحة معا."
-#: geniza/templates/admin/base_site.html:4
+#: templates/admin/base_site.html:4
msgid "Django site admin"
msgstr "مسؤول موقع Django"
-#: geniza/templates/base.html:91
+#: templates/base.html:91
msgid "Skip to main content"
msgstr "تخطي إلى المحتوى الرئيسي"
#. Translators: accessibility text label for footer site navigation
-#: geniza/templates/footer.html:4
+#: templates/footer.html:4
msgid "footer navigation"
msgstr "التنقل في التذييل"
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: geniza/templates/footer.html:21
+#: templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr "الصفحة الرئيسية لـ Princeton Geniza Lab"
-#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
+#: templates/footer.html:28 templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: geniza/templates/footer.html:35
+#: templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr "الصفحة الرئيسية لـ Princeton Center for Digital Humanities"
-#: geniza/templates/footer.html:46
+#: templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: geniza/templates/footer.html:56
+#: templates/footer.html:56
msgid "Accessibility"
msgstr "إمكانية الوصول"
#. Translators: official name of Princeton University's board of trustees, for footer
-#: geniza/templates/footer.html:61
+#: templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr "The Trustees of Princeton University"
#. Translators: Creative Commons license text, for footer
-#: geniza/templates/footer.html:65
+#: templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr "ترخيص Creative Commons CC-BY"
#. Translators: International Standard Serial Number (ISSN), for footer
-#: geniza/templates/footer.html:72
+#: templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr "ISSN: 2834-4146"
#. Translators: software version, for footer
-#: geniza/templates/footer.html:76
+#: templates/footer.html:76
msgid "software version"
msgstr "إصدار البرنامج"
#. Translators: official name of Princeton University, for footer
-#: geniza/templates/footer.html:83
+#: templates/footer.html:83
msgid "Princeton University homepage"
msgstr "الصفحة الرئيسية لـ Princeton University"
#. Translators: accessibility text label for site navigation
-#: geniza/templates/nav.html:4
+#: templates/nav.html:4
msgid "main navigation"
msgstr "التنقل الرئيسي"
#. Translators: accessibility label for 'home' button in main navigation
-#: geniza/templates/nav.html:11
+#: templates/nav.html:11
msgid "home"
msgstr "الصفحة الرئيسية"
#. Translators: site name label for 'home' button in main navigation
-#: geniza/templates/nav.html:13
+#: templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr "The Princeton Geniza Project"
#. Translators: label for 'menu' open button for mobile navigation
-#: geniza/templates/nav.html:24
+#: templates/nav.html:24
msgid "Open main navigation menu"
msgstr "فتح قائمة التنقل الرئيسية"
#. Translators: label for 'menu' close button for mobile navigation
-#: geniza/templates/nav.html:32
+#: templates/nav.html:32
msgid "Close main navigation menu"
msgstr "إغلاق قائمة التنقل الرئيسية"
#. Translators: label for language choices in navigation
-#: geniza/templates/snippets/language_switcher.html:11
+#: templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr "قراءة هذه الصفحة في %(language_name)s (%(language_code)s)"
#. Translators: label for link to 'Search' page in main navigation
-#: geniza/templates/snippets/menu.html:7
+#: templates/snippets/menu.html:7
msgid "Search"
msgstr "بحث"
#. Translators: label for main menu back button for mobile navigation
-#: geniza/templates/snippets/sub_menu.html:8
+#: templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr "العودة إلى القائمة الرئيسية"
#. Translators: label for dark mode/light mode theme switcher
-#: geniza/templates/snippets/theme_toggle.html:5
+#: templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "تفعيل الوضع المظلم"
diff --git a/geniza/locale/en/LC_MESSAGES/django.po b/geniza/locale/en/LC_MESSAGES/django.po
index e59bf9f88..2484fa89d 100644
--- a/geniza/locale/en/LC_MESSAGES/django.po
+++ b/geniza/locale/en/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: geniza\n"
"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
-"POT-Creation-Date: 2024-03-15 11:03-0400\n"
+"POT-Creation-Date: 2024-03-15 12:29-0400\n"
"PO-Revision-Date: 2022-08-01 14:25\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -18,217 +18,214 @@ msgstr ""
"X-Crowdin-File-ID: 28\n"
#. Translators: placeholder for keyword search input
-#: geniza/corpus/forms.py:164
+#: corpus/forms.py:164
msgid "search by keyword"
msgstr ""
#. Translators: accessible label for keyword search input
-#: geniza/corpus/forms.py:166
+#: corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr ""
#. Translators: label for sort by relevance
-#: geniza/corpus/forms.py:175
+#: corpus/forms.py:175
msgid "Relevance"
msgstr ""
#. Translators: label for sort in random order
-#: geniza/corpus/forms.py:177
+#: corpus/forms.py:177
msgid "Random"
msgstr ""
#. Translators: label for sort by document date (most recent first)
-#: geniza/corpus/forms.py:179
+#: corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr ""
#. Translators: label for sort by document date (oldest first)
-#: geniza/corpus/forms.py:181
+#: corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr ""
#. Translators: label for alphabetical sort by shelfmark
-#: geniza/corpus/forms.py:183
+#: corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr ""
#. Translators: label for descending sort by number of scholarship records
-#: geniza/corpus/forms.py:185
+#: corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr ""
#. Translators: label for ascending sort by number of scholarship records
-#: geniza/corpus/forms.py:187
+#: corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr ""
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: geniza/corpus/forms.py:189
+#: corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr ""
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: geniza/corpus/forms.py:191
+#: corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr ""
#. Translators: label for start year when filtering by date range
-#: geniza/corpus/forms.py:194
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:194 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr ""
#. Translators: label for end year when filtering by date range
-#: geniza/corpus/forms.py:196
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:196 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr ""
#. Translators: label for form sort field
-#: geniza/corpus/forms.py:204
+#: corpus/forms.py:204
msgid "Sort by"
msgstr ""
-#: geniza/corpus/forms.py:211
+#: corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr ""
#. Translators: label for document type search form filter
-#: geniza/corpus/forms.py:220
+#: corpus/forms.py:220
msgid "Document Type"
msgstr ""
#. Translators: label for "has image" search form filter
-#: geniza/corpus/forms.py:224
+#: corpus/forms.py:224
msgid "Has Image"
msgstr ""
#. Translators: label for "has transcription" search form filter
-#: geniza/corpus/forms.py:228
+#: corpus/forms.py:228
msgid "Has Transcription"
msgstr ""
#. Translators: label for "has translation" search form filter
-#: geniza/corpus/forms.py:232
+#: corpus/forms.py:232
msgid "Has Translation"
msgstr ""
#. Translators: label for "has discussion" search form filter
-#: geniza/corpus/forms.py:236
+#: corpus/forms.py:236
msgid "Has Discussion"
msgstr ""
#. Translators: Default label when document does not have a type
-#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
-#: geniza/corpus/templates/corpus/snippets/document_header.html:17
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
+#: corpus/forms.py:282 corpus/solr_queryset.py:240
+#: corpus/templates/corpus/snippets/document_header.html:17
+#: corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr ""
-#: geniza/corpus/forms.py:314
+#: corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr ""
-#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
+#: corpus/models.py:1081 templates/base.html:21
msgid "Princeton Geniza Project"
msgstr ""
#. Translators: attribution for local IIIF manifests
-#: geniza/corpus/models.py:1083
+#: corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr ""
#. Translators: attribution for local IIIF manifests that include transcription
-#: geniza/corpus/models.py:1086
+#: corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr ""
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: geniza/corpus/models.py:1088
+#: corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr ""
#. Translators: label for 'home' link in footer navigation
-#: geniza/corpus/templates/admin/corpus/app_index.html:9
-#: geniza/templates/footer.html:12
+#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
msgid "Home"
msgstr "Home"
#. Translators: Other documents on the same fragment/shelfmark
-#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
+#: corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] ""
msgstr[1] ""
#. Translators: Editor label
-#: geniza/corpus/templates/corpus/document_detail.html:17
-#: geniza/corpus/templates/corpus/document_detail.html:50
+#: corpus/templates/corpus/document_detail.html:17
+#: corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] ""
msgstr[1] ""
#. Translators: label for document metadata section (editor, date, input date)
-#: geniza/corpus/templates/corpus/document_detail.html:41
+#: corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr ""
-#: geniza/corpus/templates/corpus/document_detail.html:45
+#: corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr ""
#. Translators: label for date of this document, if known
-#: geniza/corpus/templates/corpus/document_detail.html:69
+#: corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr ""
#. Translators: Inferred dating label
-#: geniza/corpus/templates/corpus/document_detail.html:80
+#: corpus/templates/corpus/document_detail.html:80
msgid "Inferred Date"
msgid_plural "Inferred Dates"
msgstr[0] ""
msgstr[1] ""
#. Translators: Primary language label
-#: geniza/corpus/templates/corpus/document_detail.html:97
+#: corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] ""
msgstr[1] ""
#. Translators: Secondary language label
-#: geniza/corpus/templates/corpus/document_detail.html:110
+#: corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] ""
msgstr[1] ""
#. Translators: label for tags on a document
-#: geniza/corpus/templates/corpus/document_detail.html:126
-#: geniza/corpus/templates/corpus/snippets/document_result.html:137
+#: corpus/templates/corpus/document_detail.html:126
+#: corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr ""
#. Translators: label for secondary/historiographical metadata
-#: geniza/corpus/templates/corpus/document_detail.html:140
+#: corpus/templates/corpus/document_detail.html:140
msgid "Additional metadata"
msgstr ""
#. Translators: label for historical/old shelfmarks on document fragments
-#: geniza/corpus/templates/corpus/document_detail.html:145
+#: corpus/templates/corpus/document_detail.html:145
msgid "Historical shelfmarks"
msgstr ""
#. Translators: Label for date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:149
+#: corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "Input date"
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:152
+#: corpus/templates/corpus/document_detail.html:152
#, python-format
msgid ""
"\n"
@@ -237,36 +234,36 @@ msgid ""
msgstr ""
#. Translators: label for document description
-#: geniza/corpus/templates/corpus/document_detail.html:162
+#: corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr ""
#. Translators: label for permanent link to a document
-#: geniza/corpus/templates/corpus/document_detail.html:174
+#: corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr ""
#. Translators: Search submit button
-#: geniza/corpus/templates/corpus/document_list.html:14
+#: corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr ""
-#: geniza/corpus/templates/corpus/document_list.html:19
-#: geniza/corpus/templates/corpus/document_list.html:30
+#: corpus/templates/corpus/document_list.html:19
+#: corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr ""
#. Translators: label for 'filters' close button for mobile navigation
-#: geniza/corpus/templates/corpus/document_list.html:25
+#: corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr ""
-#: geniza/corpus/templates/corpus/document_list.html:60
+#: corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr ""
#. Translators: number of search results
-#: geniza/corpus/templates/corpus/document_list.html:87
+#: corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -274,76 +271,76 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: screen reader label for pagination navigation displayed after search results
-#: geniza/corpus/templates/corpus/document_list.html:103
+#: corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: geniza/corpus/templates/corpus/document_scholarship.html:21
+#: corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr ""
#. Translators: label for included document relations for a single footnote
-#: geniza/corpus/templates/corpus/document_scholarship.html:28
+#: corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr ""
#. Translators: label for document relations in list of footnotes
-#: geniza/corpus/templates/corpus/document_scholarship.html:31
+#: corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr ""
#. Translators: label for document relations for one footnote with no location or URL
-#: geniza/corpus/templates/corpus/document_scholarship.html:36
+#: corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr ""
#. Translators: Document edit link for admins
-#: geniza/corpus/templates/corpus/snippets/document_header.html:9
+#: corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
+#: corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
+#: corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
+#: corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
+#: corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#: corpus/templates/corpus/snippets/document_result.html:22
#, fuzzy
#| msgid "Input date"
msgid "Document date"
msgstr "Input date"
#. Translators: label for historical/old shelfmark on document fragments
-#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+#: corpus/templates/corpus/snippets/document_result.html:33
msgid "Historical shelfmark"
msgstr ""
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:38
+#: corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr ""
#. Translators: label for unknown date for date added to PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:40
+#: corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:102
+#: corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -351,7 +348,7 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: number of translations for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:112
+#: corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -359,124 +356,124 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: number of sources that discuss this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:122
+#: corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
msgstr[0] ""
msgstr[1] ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:130
+#: corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:144
+#: corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr ""
#. Translators: screen-reader label for "view document details" link
-#: geniza/corpus/templates/corpus/snippets/document_result.html:168
+#: corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:170
+#: corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr ""
#. Translators: accessibility text label for document detail view tabs navigation
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
+#: corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
+#: corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr ""
#. Translators: n_records is number of scholarship records
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
+#: corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
+#: corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
+#: corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
+#: corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
+#: corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
+#: corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
+#: corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
+#: corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
-#: geniza/footnotes/models.py:495
+#: corpus/templates/corpus/snippets/document_transcription.html:239
+#: footnotes/models.py:495
msgid "Translation"
msgstr ""
#. Translators: label for a link to a resource with no named location
-#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
+#: corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr ""
#. Translators: screen reader label for pagination navigation
-#: geniza/corpus/templates/corpus/snippets/pagination.html:4
+#: corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:8
+#: corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr ""
#. Translators: Title for link to page number in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:22
+#: corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr ""
#. Translators: Label for "next page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:63
+#: corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr ""
#. Translators: title of document search page
-#: geniza/corpus/views.py:49
+#: corpus/views.py:49
msgid "Search Documents"
msgstr "Search Documents"
#. Translators: description of document search page, for search engines
-#: geniza/corpus/views.py:51
+#: corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr ""
#. Translators: title of document scholarship page
-#: geniza/corpus/views.py:382
+#: corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr ""
-#: geniza/corpus/views.py:389
+#: corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -484,70 +481,70 @@ msgstr[0] ""
msgstr[1] ""
#. Translators: title of related documents page
-#: geniza/corpus/views.py:430
+#: corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr ""
-#: geniza/corpus/views.py:437
+#: corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
msgstr[0] ""
msgstr[1] ""
-#: geniza/entities/models.py:144
+#: entities/models.py:144
msgid "Male"
msgstr ""
-#: geniza/entities/models.py:145
+#: entities/models.py:145
msgid "Female"
msgstr ""
-#: geniza/entities/models.py:146
+#: entities/models.py:146
msgid "Unknown"
msgstr ""
-#: geniza/entities/models.py:412
+#: entities/models.py:412
msgid "Immediate family relations"
msgstr ""
-#: geniza/entities/models.py:413
+#: entities/models.py:413
msgid "Extended family"
msgstr ""
-#: geniza/entities/models.py:414
+#: entities/models.py:414
msgid "Relatives by marriage"
msgstr ""
-#: geniza/entities/models.py:415
+#: entities/models.py:415
msgid "Business and property relationships"
msgstr ""
-#: geniza/entities/models.py:416
+#: entities/models.py:416
msgid "Ambiguity"
msgstr ""
#. Translators: Placeholder for when a work has no title available
-#: geniza/footnotes/models.py:259
+#: footnotes/models.py:259
msgid "[digital geniza document edition]"
msgstr ""
-#: geniza/footnotes/models.py:494
+#: footnotes/models.py:494
msgid "Edition"
msgstr ""
-#: geniza/footnotes/models.py:496
+#: footnotes/models.py:496
msgid "Discussion"
msgstr ""
-#: geniza/pages/models.py:13
+#: pages/models.py:13
msgid ""
"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
msgstr ""
-#: geniza/pages/models.py:44
+#: pages/models.py:44
msgid ""
"This text will only be read to non-sighted users and should describe the "
"major insights or takeaways from the graphic. Multiple paragraphs are "
@@ -555,130 +552,130 @@ msgid ""
msgstr ""
#. Translators: title for Not Found (404) error page
-#: geniza/templates/404.html:5 geniza/templates/404.html:13
+#: templates/404.html:5 templates/404.html:13
msgid "Not Found"
msgstr ""
#. Translators: description for Not Found (404) error page
-#: geniza/templates/404.html:7 geniza/templates/404.html:14
+#: templates/404.html:7 templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr ""
#. Translators: title for Internal Server Error (500) error page
-#: geniza/templates/500.html:5 geniza/templates/500.html:19
+#: templates/500.html:5 templates/500.html:19
msgid "Server Error"
msgstr ""
#. Translators: description for Internal Server Error (500) error page
-#: geniza/templates/500.html:7 geniza/templates/500.html:20
+#: templates/500.html:7 templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr ""
-#: geniza/templates/admin/base_site.html:4
+#: templates/admin/base_site.html:4
msgid "Django site admin"
msgstr ""
-#: geniza/templates/base.html:91
+#: templates/base.html:91
msgid "Skip to main content"
msgstr ""
#. Translators: accessibility text label for footer site navigation
-#: geniza/templates/footer.html:4
+#: templates/footer.html:4
msgid "footer navigation"
msgstr ""
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: geniza/templates/footer.html:21
+#: templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr ""
-#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
+#: templates/footer.html:28 templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: geniza/templates/footer.html:35
+#: templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr ""
-#: geniza/templates/footer.html:46
+#: templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: geniza/templates/footer.html:56
+#: templates/footer.html:56
msgid "Accessibility"
msgstr ""
#. Translators: official name of Princeton University's board of trustees, for footer
-#: geniza/templates/footer.html:61
+#: templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr ""
#. Translators: Creative Commons license text, for footer
-#: geniza/templates/footer.html:65
+#: templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr ""
#. Translators: International Standard Serial Number (ISSN), for footer
-#: geniza/templates/footer.html:72
+#: templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr ""
#. Translators: software version, for footer
-#: geniza/templates/footer.html:76
+#: templates/footer.html:76
msgid "software version"
msgstr ""
#. Translators: official name of Princeton University, for footer
-#: geniza/templates/footer.html:83
+#: templates/footer.html:83
msgid "Princeton University homepage"
msgstr ""
#. Translators: accessibility text label for site navigation
-#: geniza/templates/nav.html:4
+#: templates/nav.html:4
msgid "main navigation"
msgstr ""
#. Translators: accessibility label for 'home' button in main navigation
-#: geniza/templates/nav.html:11
+#: templates/nav.html:11
msgid "home"
msgstr ""
#. Translators: site name label for 'home' button in main navigation
-#: geniza/templates/nav.html:13
+#: templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr ""
#. Translators: label for 'menu' open button for mobile navigation
-#: geniza/templates/nav.html:24
+#: templates/nav.html:24
msgid "Open main navigation menu"
msgstr ""
#. Translators: label for 'menu' close button for mobile navigation
-#: geniza/templates/nav.html:32
+#: templates/nav.html:32
msgid "Close main navigation menu"
msgstr ""
#. Translators: label for language choices in navigation
-#: geniza/templates/snippets/language_switcher.html:11
+#: templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr ""
#. Translators: label for link to 'Search' page in main navigation
-#: geniza/templates/snippets/menu.html:7
+#: templates/snippets/menu.html:7
msgid "Search"
msgstr ""
#. Translators: label for main menu back button for mobile navigation
-#: geniza/templates/snippets/sub_menu.html:8
+#: templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr ""
#. Translators: label for dark mode/light mode theme switcher
-#: geniza/templates/snippets/theme_toggle.html:5
+#: templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr ""
diff --git a/geniza/locale/he/LC_MESSAGES/django.po b/geniza/locale/he/LC_MESSAGES/django.po
index c11212e63..cb2a2879f 100644
--- a/geniza/locale/he/LC_MESSAGES/django.po
+++ b/geniza/locale/he/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
-"POT-Creation-Date: 2024-03-15 11:03-0400\n"
+"POT-Creation-Date: 2024-03-15 12:29-0400\n"
"PO-Revision-Date: 2024-02-12 23:10\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
@@ -19,146 +19,143 @@ msgstr ""
"X-Crowdin-File-ID: 12\n"
#. Translators: placeholder for keyword search input
-#: geniza/corpus/forms.py:164
+#: corpus/forms.py:164
msgid "search by keyword"
msgstr "חיפוש לפי מילת מפתח"
#. Translators: accessible label for keyword search input
-#: geniza/corpus/forms.py:166
+#: corpus/forms.py:166
msgid "Keyword or Phrase"
msgstr "מילת מפתח או ביטוי"
#. Translators: label for sort by relevance
-#: geniza/corpus/forms.py:175
+#: corpus/forms.py:175
msgid "Relevance"
msgstr "רלוונטיות"
#. Translators: label for sort in random order
-#: geniza/corpus/forms.py:177
+#: corpus/forms.py:177
msgid "Random"
msgstr "אקראי"
#. Translators: label for sort by document date (most recent first)
-#: geniza/corpus/forms.py:179
+#: corpus/forms.py:179
msgid "Document Date (Latest–Earliest)"
msgstr "תאריך המסמך (מאוחר ביותר - מוקדם ביותר)"
#. Translators: label for sort by document date (oldest first)
-#: geniza/corpus/forms.py:181
+#: corpus/forms.py:181
msgid "Document Date (Earliest–Latest)"
msgstr "תאריך המסמך (מוקדם ביותר - מאוחר ביותר)"
#. Translators: label for alphabetical sort by shelfmark
-#: geniza/corpus/forms.py:183
+#: corpus/forms.py:183
msgid "Shelfmark (A–Z)"
msgstr "מספר מדף (A-Z)"
#. Translators: label for descending sort by number of scholarship records
-#: geniza/corpus/forms.py:185
+#: corpus/forms.py:185
msgid "Scholarship Records (Most–Least)"
msgstr "רשומות קשורות (מהגדול לקטן)"
#. Translators: label for ascending sort by number of scholarship records
-#: geniza/corpus/forms.py:187
+#: corpus/forms.py:187
msgid "Scholarship Records (Least–Most)"
msgstr "רשומות קשורות (מהקטן לגדול)"
#. Translators: label for sort by when document was added to PGP (most recent first)
-#: geniza/corpus/forms.py:189
+#: corpus/forms.py:189
msgid "PGP Input Date (Latest–Earliest)"
msgstr "תאריך הוספה (מאוחר ביותר - מוקדם ביותר)"
#. Translators: label for sort by when document was added to PGP (oldest first)
-#: geniza/corpus/forms.py:191
+#: corpus/forms.py:191
msgid "PGP Input Date (Earliest–Latest)"
msgstr "תאריך הוספה (מוקדם ביותר - מאוחר ביותר)"
#. Translators: label for start year when filtering by date range
-#: geniza/corpus/forms.py:194
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:194 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "From year"
msgstr "משנת"
#. Translators: label for end year when filtering by date range
-#: geniza/corpus/forms.py:196
-#: geniza/corpus/templates/corpus/widgets/yearrangewidget.html:5
+#: corpus/forms.py:196 corpus/templates/corpus/widgets/yearrangewidget.html:5
msgid "To year"
msgstr "עד שנת"
#. Translators: label for form sort field
-#: geniza/corpus/forms.py:204
+#: corpus/forms.py:204
msgid "Sort by"
msgstr "מיון לפי"
-#: geniza/corpus/forms.py:211
+#: corpus/forms.py:211
msgid "Document Dates (CE)"
msgstr "תאריכי המסמך (CE)"
#. Translators: label for document type search form filter
-#: geniza/corpus/forms.py:220
+#: corpus/forms.py:220
msgid "Document Type"
msgstr "סוג המסמך"
#. Translators: label for "has image" search form filter
-#: geniza/corpus/forms.py:224
+#: corpus/forms.py:224
msgid "Has Image"
msgstr "קיים תצלום"
#. Translators: label for "has transcription" search form filter
-#: geniza/corpus/forms.py:228
+#: corpus/forms.py:228
msgid "Has Transcription"
msgstr "קיים תיעתוק"
#. Translators: label for "has translation" search form filter
-#: geniza/corpus/forms.py:232
+#: corpus/forms.py:232
msgid "Has Translation"
msgstr "קיים תרגום"
#. Translators: label for "has discussion" search form filter
-#: geniza/corpus/forms.py:236
+#: corpus/forms.py:236
msgid "Has Discussion"
msgstr "קיים דיון"
#. Translators: Default label when document does not have a type
-#: geniza/corpus/forms.py:282 geniza/corpus/solr_queryset.py:240
-#: geniza/corpus/templates/corpus/snippets/document_header.html:17
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:173
+#: corpus/forms.py:282 corpus/solr_queryset.py:240
+#: corpus/templates/corpus/snippets/document_header.html:17
+#: corpus/templates/corpus/snippets/document_transcription.html:173
msgid "Unknown type"
msgstr "סוג לא ידוע"
-#: geniza/corpus/forms.py:314
+#: corpus/forms.py:314
msgid "Relevance sort is not available without a keyword search term."
msgstr "מיון על פי רלוונטיות אינו זמין ללא מילת מפתח לחיפוש."
-#: geniza/corpus/models.py:1081 geniza/templates/base.html:21
+#: corpus/models.py:1081 templates/base.html:21
msgid "Princeton Geniza Project"
msgstr "Princeton Geniza Project"
#. Translators: attribution for local IIIF manifests
-#: geniza/corpus/models.py:1083
+#: corpus/models.py:1083
#, python-format
msgid "Compilation by %(pgp)s."
msgstr "קובץ על-ידי %(pgp)s."
#. Translators: attribution for local IIIF manifests that include transcription
-#: geniza/corpus/models.py:1086
+#: corpus/models.py:1086
#, python-format
msgid "Compilation and transcription by %(pgp)s."
msgstr "קובץ ותועתק על-ידי %(pgp)s."
#. Translators: manifest attribution note that content from other institutions may have restrictions
-#: geniza/corpus/models.py:1088
+#: corpus/models.py:1088
msgid "Additional restrictions may apply."
msgstr "ייתכנו מגבלות נוספות."
#. Translators: label for 'home' link in footer navigation
-#: geniza/corpus/templates/admin/corpus/app_index.html:9
-#: geniza/templates/footer.html:12
+#: corpus/templates/admin/corpus/app_index.html:9 templates/footer.html:12
msgid "Home"
msgstr "דף הבית"
#. Translators: Other documents on the same fragment/shelfmark
-#: geniza/corpus/templates/admin/corpus/document/change_form.html:49
+#: corpus/templates/admin/corpus/document/change_form.html:49
msgid "Other documents on this shelfmark"
msgid_plural "Other documents on these shelfmarks"
msgstr[0] "מסמכים נוספים עם מספר מדף זה"
@@ -167,8 +164,8 @@ msgstr[2] "מסמכים נוספים עם מספרי מדף אלו"
msgstr[3] "מסמכים נוספים עם מספרי מדף אלו"
#. Translators: Editor label
-#: geniza/corpus/templates/corpus/document_detail.html:17
-#: geniza/corpus/templates/corpus/document_detail.html:50
+#: corpus/templates/corpus/document_detail.html:17
+#: corpus/templates/corpus/document_detail.html:50
msgid "Editor"
msgid_plural "Editors"
msgstr[0] "עורך"
@@ -177,21 +174,21 @@ msgstr[2] "עורכים"
msgstr[3] "עורכים"
#. Translators: label for document metadata section (editor, date, input date)
-#: geniza/corpus/templates/corpus/document_detail.html:41
+#: corpus/templates/corpus/document_detail.html:41
msgid "Metadata"
msgstr "מטא-דאטא"
-#: geniza/corpus/templates/corpus/document_detail.html:45
+#: corpus/templates/corpus/document_detail.html:45
msgid "Shelfmark"
msgstr "מספר מדף"
#. Translators: label for date of this document, if known
-#: geniza/corpus/templates/corpus/document_detail.html:69
+#: corpus/templates/corpus/document_detail.html:69
msgid "Document Date"
msgstr "תאריך המסמך"
#. Translators: Inferred dating label
-#: geniza/corpus/templates/corpus/document_detail.html:80
+#: corpus/templates/corpus/document_detail.html:80
msgid "Inferred Date"
msgid_plural "Inferred Dates"
msgstr[0] ""
@@ -200,7 +197,7 @@ msgstr[2] ""
msgstr[3] ""
#. Translators: Primary language label
-#: geniza/corpus/templates/corpus/document_detail.html:97
+#: corpus/templates/corpus/document_detail.html:97
msgid "Primary Language"
msgid_plural "Primary Languages"
msgstr[0] "שפה עיקרית"
@@ -209,7 +206,7 @@ msgstr[2] "שפות עיקריות"
msgstr[3] "שפות עיקריות"
#. Translators: Secondary language label
-#: geniza/corpus/templates/corpus/document_detail.html:110
+#: corpus/templates/corpus/document_detail.html:110
msgid "Secondary Language"
msgid_plural "Secondary Languages"
msgstr[0] "שפה משנית"
@@ -218,28 +215,28 @@ msgstr[2] "שפות משניות"
msgstr[3] "שפות משניות"
#. Translators: label for tags on a document
-#: geniza/corpus/templates/corpus/document_detail.html:126
-#: geniza/corpus/templates/corpus/snippets/document_result.html:137
+#: corpus/templates/corpus/document_detail.html:126
+#: corpus/templates/corpus/snippets/document_result.html:137
msgid "Tags"
msgstr "תגים"
#. Translators: label for secondary/historiographical metadata
-#: geniza/corpus/templates/corpus/document_detail.html:140
+#: corpus/templates/corpus/document_detail.html:140
msgid "Additional metadata"
msgstr ""
#. Translators: label for historical/old shelfmarks on document fragments
-#: geniza/corpus/templates/corpus/document_detail.html:145
+#: corpus/templates/corpus/document_detail.html:145
msgid "Historical shelfmarks"
msgstr ""
#. Translators: Label for date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:149
+#: corpus/templates/corpus/document_detail.html:149
msgid "Input date"
msgstr "תאריך קלט"
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/document_detail.html:152
+#: corpus/templates/corpus/document_detail.html:152
#, fuzzy, python-format
#| msgid ""
#| "\n"
@@ -255,36 +252,36 @@ msgstr ""
" "
#. Translators: label for document description
-#: geniza/corpus/templates/corpus/document_detail.html:162
+#: corpus/templates/corpus/document_detail.html:162
msgid "Description"
msgstr "תיאור"
#. Translators: label for permanent link to a document
-#: geniza/corpus/templates/corpus/document_detail.html:174
+#: corpus/templates/corpus/document_detail.html:174
msgid "Permalink"
msgstr "קישור קבוע"
#. Translators: Search submit button
-#: geniza/corpus/templates/corpus/document_list.html:14
+#: corpus/templates/corpus/document_list.html:14
msgid "Submit search"
msgstr "חיפוש"
-#: geniza/corpus/templates/corpus/document_list.html:19
-#: geniza/corpus/templates/corpus/document_list.html:30
+#: corpus/templates/corpus/document_list.html:19
+#: corpus/templates/corpus/document_list.html:30
msgid "Filters"
msgstr "מסננים"
#. Translators: label for 'filters' close button for mobile navigation
-#: geniza/corpus/templates/corpus/document_list.html:25
+#: corpus/templates/corpus/document_list.html:25
msgid "Close filter options"
msgstr "סגור אפשרויות סינון"
-#: geniza/corpus/templates/corpus/document_list.html:60
+#: corpus/templates/corpus/document_list.html:60
msgid "Apply"
msgstr "החל"
#. Translators: number of search results
-#: geniza/corpus/templates/corpus/document_list.html:87
+#: corpus/templates/corpus/document_list.html:87
#, python-format
msgid "1 result"
msgid_plural "%(count_humanized)s total results"
@@ -294,76 +291,76 @@ msgstr[2] "%(count_humanized)s תוצאות"
msgstr[3] "%(count_humanized)s תוצאות"
#. Translators: screen reader label for pagination navigation displayed after search results
-#: geniza/corpus/templates/corpus/document_list.html:103
+#: corpus/templates/corpus/document_list.html:103
msgid "secondary pagination"
msgstr ""
#. Translators: accessibility label for a footnote source citation in scholarship records view
-#: geniza/corpus/templates/corpus/document_scholarship.html:21
+#: corpus/templates/corpus/document_scholarship.html:21
msgid "Bibliographic citation"
msgstr "ציטוט"
#. Translators: label for included document relations for a single footnote
-#: geniza/corpus/templates/corpus/document_scholarship.html:28
+#: corpus/templates/corpus/document_scholarship.html:28
msgid "includes"
msgstr "כלול"
#. Translators: label for document relations in list of footnotes
-#: geniza/corpus/templates/corpus/document_scholarship.html:31
+#: corpus/templates/corpus/document_scholarship.html:31
#, python-format
msgid "for %(relation)s see"
msgstr "ל%(relation)s ראה"
#. Translators: label for document relations for one footnote with no location or URL
-#: geniza/corpus/templates/corpus/document_scholarship.html:36
+#: corpus/templates/corpus/document_scholarship.html:36
#, python-format
msgid "includes %(relation)s"
msgstr "כולל %(relation)s"
#. Translators: Document edit link for admins
-#: geniza/corpus/templates/corpus/snippets/document_header.html:9
+#: corpus/templates/corpus/snippets/document_header.html:9
msgid "Edit"
msgstr "עריכה"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:4
+#: corpus/templates/corpus/snippets/document_image_rights.html:4
msgid "Image Permissions Statement"
msgstr "תנאי היתר שימוש בתצלום"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:21
+#: corpus/templates/corpus/snippets/document_image_rights.html:21
msgid "No attribution or license noted."
msgstr "לא מצוין ייחוס או רישיון."
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:38
+#: corpus/templates/corpus/snippets/document_image_rights.html:38
msgid "Jewish Theological Seminary homepage"
msgstr "עמוד הבית Jewish Theological Seminary"
-#: geniza/corpus/templates/corpus/snippets/document_image_rights.html:40
+#: corpus/templates/corpus/snippets/document_image_rights.html:40
msgid "Jewish Theological Seminary logo"
msgstr "סמל Jewish Theological Seminary"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:22
+#: corpus/templates/corpus/snippets/document_result.html:22
#, fuzzy
#| msgid "Document Date"
msgid "Document date"
msgstr "תאריך המסמך"
#. Translators: label for historical/old shelfmark on document fragments
-#: geniza/corpus/templates/corpus/snippets/document_result.html:33
+#: corpus/templates/corpus/snippets/document_result.html:33
msgid "Historical shelfmark"
msgstr ""
#. Translators: Date document was first added to the PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:38
+#: corpus/templates/corpus/snippets/document_result.html:38
msgid "In PGP since"
msgstr "נמצא בPGP מאז"
#. Translators: label for unknown date for date added to PGP
-#: geniza/corpus/templates/corpus/snippets/document_result.html:40
+#: corpus/templates/corpus/snippets/document_result.html:40
msgid "unknown"
msgstr ""
#. Translators: number of editions for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:102
+#: corpus/templates/corpus/snippets/document_result.html:102
#, python-format
msgid "1 Transcription"
msgid_plural "%(counter)s Transcriptions"
@@ -373,7 +370,7 @@ msgstr[2] "%(counter)s תעתוקים"
msgstr[3] "%(counter)s תעתוקים"
#. Translators: number of translations for this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:112
+#: corpus/templates/corpus/snippets/document_result.html:112
#, python-format
msgid "1 Translation"
msgid_plural "%(counter)s Translations"
@@ -383,7 +380,7 @@ msgstr[2] "%(counter)s תרגומים"
msgstr[3] "%(counter)s תרגומים"
#. Translators: number of sources that discuss this document
-#: geniza/corpus/templates/corpus/snippets/document_result.html:122
+#: corpus/templates/corpus/snippets/document_result.html:122
#, python-format
msgid "1 Discussion"
msgid_plural "%(counter)s Discussions"
@@ -392,117 +389,117 @@ msgstr[1] "%(counter)s דיונים"
msgstr[2] "%(counter)s דיונים"
msgstr[3] "%(counter)s דיונים"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:130
+#: corpus/templates/corpus/snippets/document_result.html:130
msgid "No Scholarship Records"
msgstr "אין רשומות קשורות"
-#: geniza/corpus/templates/corpus/snippets/document_result.html:144
+#: corpus/templates/corpus/snippets/document_result.html:144
msgid "more"
msgstr "עוד"
#. Translators: screen-reader label for "view document details" link
-#: geniza/corpus/templates/corpus/snippets/document_result.html:168
+#: corpus/templates/corpus/snippets/document_result.html:168
#, python-format
msgid "View details for %(document_label)s"
msgstr ""
-#: geniza/corpus/templates/corpus/snippets/document_result.html:170
+#: corpus/templates/corpus/snippets/document_result.html:170
msgid "View document details"
msgstr "הצגת פרטי מסמך"
#. Translators: accessibility text label for document detail view tabs navigation
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:4
+#: corpus/templates/corpus/snippets/document_tabs.html:4
msgid "tabs"
msgstr "כרטיסיות"
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:7
+#: corpus/templates/corpus/snippets/document_tabs.html:7
msgid "Document Details"
msgstr "פרטי מסמך"
#. Translators: n_records is number of scholarship records
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:10
+#: corpus/templates/corpus/snippets/document_tabs.html:10
#, python-format
msgid "Scholarship Records (%(n_records)s)"
msgstr "רשומות קשורות (%(n_records)s)"
-#: geniza/corpus/templates/corpus/snippets/document_tabs.html:17
+#: corpus/templates/corpus/snippets/document_tabs.html:17
#, python-format
msgid "Related Documents (%(n_reldocs)s)"
msgstr "מסמכים קשורים (%(n_reldocs)s)"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:39
+#: corpus/templates/corpus/snippets/document_transcription.html:39
msgid "show images"
msgstr "הצג תמונות"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:42
+#: corpus/templates/corpus/snippets/document_transcription.html:42
msgid "show transcription"
msgstr "הצג תעתוק"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:46
+#: corpus/templates/corpus/snippets/document_transcription.html:46
msgid "show translation"
msgstr "הצג תרגום"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:142
+#: corpus/templates/corpus/snippets/document_transcription.html:142
msgid "Zoom and Rotate"
msgstr "הגדל וסובב"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:176
+#: corpus/templates/corpus/snippets/document_transcription.html:176
#, python-format
msgid "View %(related_doc)s"
msgstr "ראה %(related_doc)s"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:200
+#: corpus/templates/corpus/snippets/document_transcription.html:200
msgid "Transcription"
msgstr "תיעתוק"
-#: geniza/corpus/templates/corpus/snippets/document_transcription.html:239
-#: geniza/footnotes/models.py:495
+#: corpus/templates/corpus/snippets/document_transcription.html:239
+#: footnotes/models.py:495
msgid "Translation"
msgstr "תרגום"
#. Translators: label for a link to a resource with no named location
-#: geniza/corpus/templates/corpus/snippets/footnote_location.html:9
+#: corpus/templates/corpus/snippets/footnote_location.html:9
msgid "online resource"
msgstr "מקור אינטרנטי"
#. Translators: screen reader label for pagination navigation
-#: geniza/corpus/templates/corpus/snippets/pagination.html:4
+#: corpus/templates/corpus/snippets/pagination.html:4
msgid "pagination"
msgstr ""
#. Translators: Label for "previous page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:8
+#: corpus/templates/corpus/snippets/pagination.html:8
msgid "Previous"
msgstr "הקודם"
#. Translators: Title for link to page number in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:22
+#: corpus/templates/corpus/snippets/pagination.html:22
#, python-format
msgid "page %(number)s"
msgstr "%(number)s עמודים"
#. Translators: Label for "next page" button in search results
-#: geniza/corpus/templates/corpus/snippets/pagination.html:63
+#: corpus/templates/corpus/snippets/pagination.html:63
msgid "Next"
msgstr "הבא"
#. Translators: title of document search page
-#: geniza/corpus/views.py:49
+#: corpus/views.py:49
msgid "Search Documents"
msgstr "חיפוש מסמכים"
#. Translators: description of document search page, for search engines
-#: geniza/corpus/views.py:51
+#: corpus/views.py:51
msgid "Search and browse Geniza documents."
msgstr "חיפוש וצפיה במסמכי הגניזה."
#. Translators: title of document scholarship page
-#: geniza/corpus/views.py:382
+#: corpus/views.py:382
#, python-format
msgid "Scholarship on %(doc)s"
msgstr "רשומה קשורה ל-%(doc)s"
-#: geniza/corpus/views.py:389
+#: corpus/views.py:389
#, python-format
msgid "%(count)d scholarship record"
msgid_plural "%(count)d scholarship records"
@@ -512,12 +509,12 @@ msgstr[2] "%(count)d רשומות קשורות"
msgstr[3] "%(count)d רשומות קשורות"
#. Translators: title of related documents page
-#: geniza/corpus/views.py:430
+#: corpus/views.py:430
#, python-format
msgid "Related documents for %(doc)s"
msgstr "מסמכים קשורים %(doc)s"
-#: geniza/corpus/views.py:437
+#: corpus/views.py:437
#, python-format
msgid "%(count)d related document"
msgid_plural "%(count)d related documents"
@@ -526,54 +523,54 @@ msgstr[1] "%(count)d מסמכים קשורים"
msgstr[2] "%(count)d מסמכים קשורים"
msgstr[3] "%(count)d מסמכים קשורים"
-#: geniza/entities/models.py:144
+#: entities/models.py:144
msgid "Male"
msgstr ""
-#: geniza/entities/models.py:145
+#: entities/models.py:145
msgid "Female"
msgstr ""
-#: geniza/entities/models.py:146
+#: entities/models.py:146
#, fuzzy
#| msgid "Unknown type"
msgid "Unknown"
msgstr "סוג לא ידוע"
-#: geniza/entities/models.py:412
+#: entities/models.py:412
msgid "Immediate family relations"
msgstr ""
-#: geniza/entities/models.py:413
+#: entities/models.py:413
msgid "Extended family"
msgstr ""
-#: geniza/entities/models.py:414
+#: entities/models.py:414
msgid "Relatives by marriage"
msgstr ""
-#: geniza/entities/models.py:415
+#: entities/models.py:415
msgid "Business and property relationships"
msgstr ""
-#: geniza/entities/models.py:416
+#: entities/models.py:416
msgid "Ambiguity"
msgstr ""
#. Translators: Placeholder for when a work has no title available
-#: geniza/footnotes/models.py:259
+#: footnotes/models.py:259
msgid "[digital geniza document edition]"
msgstr "[גרסת מסמך גניזה דיגיטלי]"
-#: geniza/footnotes/models.py:494
+#: footnotes/models.py:494
msgid "Edition"
msgstr "מהדורה"
-#: geniza/footnotes/models.py:496
+#: footnotes/models.py:496
msgid "Discussion"
msgstr "דיון"
-#: geniza/pages/models.py:13
+#: pages/models.py:13
msgid ""
"Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
@@ -581,7 +578,7 @@ msgstr ""
"טקסט אלטרנטיבי למשתמשים כבדי ראייה שמטרתו להעביר בתמצית את המסר של התצלום "
"בהקשר זה."
-#: geniza/pages/models.py:44
+#: pages/models.py:44
msgid ""
"This text will only be read to non-sighted users and should describe the "
"major insights or takeaways from the graphic. Multiple paragraphs are "
@@ -591,130 +588,130 @@ msgstr ""
"שעולים מהתוכן הגרפי. ניתן לכלול מספר פסקאות."
#. Translators: title for Not Found (404) error page
-#: geniza/templates/404.html:5 geniza/templates/404.html:13
+#: templates/404.html:5 templates/404.html:13
msgid "Not Found"
msgstr "לא נמצא"
#. Translators: description for Not Found (404) error page
-#: geniza/templates/404.html:7 geniza/templates/404.html:14
+#: templates/404.html:7 templates/404.html:14
msgid "Oops, you've hit a lacuna! Page not found."
msgstr "אופס, נתקלת בלאקונה! העמוד המבוקש לא נמצא."
#. Translators: title for Internal Server Error (500) error page
-#: geniza/templates/500.html:5 geniza/templates/500.html:19
+#: templates/500.html:5 templates/500.html:19
msgid "Server Error"
msgstr "שגיאת שרת"
#. Translators: description for Internal Server Error (500) error page
-#: geniza/templates/500.html:7 geniza/templates/500.html:20
+#: templates/500.html:7 templates/500.html:20
msgid "Something went wrong putting this page together."
msgstr "דבר מה השתבש בהפקת עמוד זה."
-#: geniza/templates/admin/base_site.html:4
+#: templates/admin/base_site.html:4
msgid "Django site admin"
msgstr "ניהול האתר"
-#: geniza/templates/base.html:91
+#: templates/base.html:91
msgid "Skip to main content"
msgstr "דילוג לתוכן"
#. Translators: accessibility text label for footer site navigation
-#: geniza/templates/footer.html:4
+#: templates/footer.html:4
msgid "footer navigation"
msgstr "ניווט בתחתית העמוד"
#. Translators: accessibility label for Princeton Geniza Lab homepage, for footer
-#: geniza/templates/footer.html:21
+#: templates/footer.html:21
msgid "Princeton Geniza Lab homepage"
msgstr "עמוד הבית Princeton Geniza Lab"
-#: geniza/templates/footer.html:28 geniza/templates/footer.html:42
+#: templates/footer.html:28 templates/footer.html:42
#, python-format
msgid "Follow %(username)s on Twitter."
msgstr ""
#. Translators: accessibility label for Princeton CDH homepage, for footer
-#: geniza/templates/footer.html:35
+#: templates/footer.html:35
msgid "Princeton Center for Digital Humanities homepage"
msgstr "Princeton Center for Digital Humanities עמוד הבית"
-#: geniza/templates/footer.html:46
+#: templates/footer.html:46
#, python-format
msgid "Follow %(username)s on Instagram."
msgstr ""
#. Translators: link label for Princeton accessibility assistance page, for footer
-#: geniza/templates/footer.html:56
+#: templates/footer.html:56
msgid "Accessibility"
msgstr "נגישות"
#. Translators: official name of Princeton University's board of trustees, for footer
-#: geniza/templates/footer.html:61
+#: templates/footer.html:61
msgid "The Trustees of Princeton University"
msgstr "The Trustees of Princeton University"
#. Translators: Creative Commons license text, for footer
-#: geniza/templates/footer.html:65
+#: templates/footer.html:65
msgid "Creative Commons CC-BY license"
msgstr "דרישת ייחוס על פי רישיון ״קריאייטיב קומונס״ (CC-BY)"
#. Translators: International Standard Serial Number (ISSN), for footer
-#: geniza/templates/footer.html:72
+#: templates/footer.html:72
msgid "ISSN: 2834-4146"
msgstr "ISSN: 2834-4146"
#. Translators: software version, for footer
-#: geniza/templates/footer.html:76
+#: templates/footer.html:76
msgid "software version"
msgstr "גרסת תוכנה"
#. Translators: official name of Princeton University, for footer
-#: geniza/templates/footer.html:83
+#: templates/footer.html:83
msgid "Princeton University homepage"
msgstr "עמוד הבית Princeton University"
#. Translators: accessibility text label for site navigation
-#: geniza/templates/nav.html:4
+#: templates/nav.html:4
msgid "main navigation"
msgstr "ניווט"
#. Translators: accessibility label for 'home' button in main navigation
-#: geniza/templates/nav.html:11
+#: templates/nav.html:11
msgid "home"
msgstr "דף הבית"
#. Translators: site name label for 'home' button in main navigation
-#: geniza/templates/nav.html:13
+#: templates/nav.html:13
msgid "The Princeton Geniza Project"
msgstr "The Princeton Geniza Project"
#. Translators: label for 'menu' open button for mobile navigation
-#: geniza/templates/nav.html:24
+#: templates/nav.html:24
msgid "Open main navigation menu"
msgstr "פתיחת תפריט ניווט"
#. Translators: label for 'menu' close button for mobile navigation
-#: geniza/templates/nav.html:32
+#: templates/nav.html:32
msgid "Close main navigation menu"
msgstr "סגירת תפריט ניווט"
#. Translators: label for language choices in navigation
-#: geniza/templates/snippets/language_switcher.html:11
+#: templates/snippets/language_switcher.html:11
#, python-format
msgid "read this page in %(language_name)s (%(language_code)s)"
msgstr "קריאת העמוד ב-%(language_name)s (%(language_code)s)"
#. Translators: label for link to 'Search' page in main navigation
-#: geniza/templates/snippets/menu.html:7
+#: templates/snippets/menu.html:7
msgid "Search"
msgstr "חיפוש"
#. Translators: label for main menu back button for mobile navigation
-#: geniza/templates/snippets/sub_menu.html:8
+#: templates/snippets/sub_menu.html:8
msgid "Return to main menu"
msgstr "חזרה לתפריט הראשי"
#. Translators: label for dark mode/light mode theme switcher
-#: geniza/templates/snippets/theme_toggle.html:5
+#: templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "הפעלת מצב כהה"
From 8d0a940f82c2910cdad77683b27132debb93c24e Mon Sep 17 00:00:00 2001
From: Rebecca Sutton Koeser
Date: Fri, 15 Mar 2024 13:13:27 -0400
Subject: [PATCH 75/97] New translations django.po (Arabic) [ci skip]
---
geniza/locale/ar/LC_MESSAGES/django.po | 42 ++++++++------------------
1 file changed, 12 insertions(+), 30 deletions(-)
diff --git a/geniza/locale/ar/LC_MESSAGES/django.po b/geniza/locale/ar/LC_MESSAGES/django.po
index a9d0f1feb..2782a6686 100644
--- a/geniza/locale/ar/LC_MESSAGES/django.po
+++ b/geniza/locale/ar/LC_MESSAGES/django.po
@@ -3,15 +3,14 @@ msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
"POT-Creation-Date: 2024-03-15 12:29-0400\n"
-"PO-Revision-Date: 2024-02-12 23:10\n"
+"PO-Revision-Date: 2024-03-15 17:13\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
-"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"X-Crowdin-Project: princeton-geniza-project\n"
"X-Crowdin-Project-ID: 520356\n"
"X-Crowdin-Language: ar\n"
@@ -247,19 +246,11 @@ msgstr "تاريخ الإدخال"
#. Translators: Date document was first added to the PGP
#: corpus/templates/corpus/document_detail.html:152
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " In PGP since %(date)s\n"
-#| " "
-msgid ""
-"\n"
+#, python-format
+msgid "\n"
" In PGP since %(date)s\n"
" "
msgstr ""
-"\n"
-" في PGP منذ %(date)s\n"
-" "
#. Translators: label for document description
#: corpus/templates/corpus/document_detail.html:162
@@ -351,10 +342,9 @@ msgid "Jewish Theological Seminary logo"
msgstr "شعار Jewish Theological Seminary"
#: corpus/templates/corpus/snippets/document_result.html:22
-#, fuzzy
-#| msgid "Document Date"
+#| msgid "Input date"
msgid "Document date"
-msgstr "تاريخ الوثيقة"
+msgstr ""
#. Translators: label for historical/old shelfmark on document fragments
#: corpus/templates/corpus/snippets/document_result.html:33
@@ -554,10 +544,8 @@ msgid "Female"
msgstr ""
#: entities/models.py:146
-#, fuzzy
-#| msgid "Unknown type"
msgid "Unknown"
-msgstr "نوع غير معروف"
+msgstr ""
#: entities/models.py:412
msgid "Immediate family relations"
@@ -593,21 +581,14 @@ msgid "Discussion"
msgstr "المناقشة"
#: pages/models.py:13
-msgid ""
-"Alternative text for visually impaired users to\n"
+msgid "Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
-msgstr ""
-"نص بديل للمستخدمين ضعاف البصر\n"
+msgstr "نص بديل للمستخدمين ضعاف البصر\n"
"لإيصال الرسالة المقصودة للصورة في هذا السياق بإيجاز."
#: pages/models.py:44
-msgid ""
-"This text will only be read to non-sighted users and should describe the "
-"major insights or takeaways from the graphic. Multiple paragraphs are "
-"allowed."
-msgstr ""
-"هذا النص سوف يقرأ فقط للمستخدمين غير المبصرين ويجب أن يصف الرؤى أو الأفكار "
-"الرئيسية من الرسوم. يسمح بفقرات متعددة."
+msgid "This text will only be read to non-sighted users and should describe the major insights or takeaways from the graphic. Multiple paragraphs are allowed."
+msgstr "هذا النص سوف يقرأ فقط للمستخدمين غير المبصرين ويجب أن يصف الرؤى أو الأفكار الرئيسية من الرسوم. يسمح بفقرات متعددة."
#. Translators: title for Not Found (404) error page
#: templates/404.html:5 templates/404.html:13
@@ -737,3 +718,4 @@ msgstr "العودة إلى القائمة الرئيسية"
#: templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "تفعيل الوضع المظلم"
+
From c6105c0182025fc9fed5a31f84d95fc6e9bc63f7 Mon Sep 17 00:00:00 2001
From: Rebecca Sutton Koeser
Date: Fri, 15 Mar 2024 13:13:28 -0400
Subject: [PATCH 76/97] New translations django.po (Hebrew) [ci skip]
---
geniza/locale/he/LC_MESSAGES/django.po | 43 +++++++-------------------
1 file changed, 12 insertions(+), 31 deletions(-)
diff --git a/geniza/locale/he/LC_MESSAGES/django.po b/geniza/locale/he/LC_MESSAGES/django.po
index cb2a2879f..560713e72 100644
--- a/geniza/locale/he/LC_MESSAGES/django.po
+++ b/geniza/locale/he/LC_MESSAGES/django.po
@@ -3,15 +3,14 @@ msgstr ""
"Project-Id-Version: princeton-geniza-project\n"
"Report-Msgid-Bugs-To: cdhdevteam@princeton.edu\n"
"POT-Creation-Date: 2024-03-15 12:29-0400\n"
-"PO-Revision-Date: 2024-02-12 23:10\n"
+"PO-Revision-Date: 2024-03-15 17:13\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Language: he_IL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
-"n%100==4 ? 2 : 3;\n"
+"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: princeton-geniza-project\n"
"X-Crowdin-Project-ID: 520356\n"
"X-Crowdin-Language: he\n"
@@ -237,19 +236,11 @@ msgstr "תאריך קלט"
#. Translators: Date document was first added to the PGP
#: corpus/templates/corpus/document_detail.html:152
-#, fuzzy, python-format
-#| msgid ""
-#| "\n"
-#| " In PGP since %(date)s\n"
-#| " "
-msgid ""
-"\n"
+#, python-format
+msgid "\n"
" In PGP since %(date)s\n"
" "
msgstr ""
-"\n"
-"נמצא בPGP מאז %(date)s\n"
-" "
#. Translators: label for document description
#: corpus/templates/corpus/document_detail.html:162
@@ -339,10 +330,9 @@ msgid "Jewish Theological Seminary logo"
msgstr "סמל Jewish Theological Seminary"
#: corpus/templates/corpus/snippets/document_result.html:22
-#, fuzzy
-#| msgid "Document Date"
+#| msgid "Input date"
msgid "Document date"
-msgstr "תאריך המסמך"
+msgstr ""
#. Translators: label for historical/old shelfmark on document fragments
#: corpus/templates/corpus/snippets/document_result.html:33
@@ -532,10 +522,8 @@ msgid "Female"
msgstr ""
#: entities/models.py:146
-#, fuzzy
-#| msgid "Unknown type"
msgid "Unknown"
-msgstr "סוג לא ידוע"
+msgstr ""
#: entities/models.py:412
msgid "Immediate family relations"
@@ -571,21 +559,13 @@ msgid "Discussion"
msgstr "דיון"
#: pages/models.py:13
-msgid ""
-"Alternative text for visually impaired users to\n"
+msgid "Alternative text for visually impaired users to\n"
"briefly communicate the intended message of the image in this context."
-msgstr ""
-"טקסט אלטרנטיבי למשתמשים כבדי ראייה שמטרתו להעביר בתמצית את המסר של התצלום "
-"בהקשר זה."
+msgstr "טקסט אלטרנטיבי למשתמשים כבדי ראייה שמטרתו להעביר בתמצית את המסר של התצלום בהקשר זה."
#: pages/models.py:44
-msgid ""
-"This text will only be read to non-sighted users and should describe the "
-"major insights or takeaways from the graphic. Multiple paragraphs are "
-"allowed."
-msgstr ""
-"טקסט זה יוקרא למשתמשים עיוורים בלבד ותכליתו לתאר את התובנות והמסרים המרכזיים "
-"שעולים מהתוכן הגרפי. ניתן לכלול מספר פסקאות."
+msgid "This text will only be read to non-sighted users and should describe the major insights or takeaways from the graphic. Multiple paragraphs are allowed."
+msgstr "טקסט זה יוקרא למשתמשים עיוורים בלבד ותכליתו לתאר את התובנות והמסרים המרכזיים שעולים מהתוכן הגרפי. ניתן לכלול מספר פסקאות."
#. Translators: title for Not Found (404) error page
#: templates/404.html:5 templates/404.html:13
@@ -715,3 +695,4 @@ msgstr "חזרה לתפריט הראשי"
#: templates/snippets/theme_toggle.html:5
msgid "Enable dark mode"
msgstr "הפעלת מצב כהה"
+
From 9daba76d69735052f67fdf3fb7724dc516c6a721 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Tue, 19 Mar 2024 11:36:05 -0400
Subject: [PATCH 77/97] Use Django conditional response to prevent mid-air
collisions (#965)
---
geniza/annotations/models.py | 17 +++++-
.../tests/test_annotations_models.py | 30 ++++++++++-
.../tests/test_annotations_views.py | 52 ++++++++++++++++++-
geniza/annotations/views.py | 41 +++++++++++++--
4 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/geniza/annotations/models.py b/geniza/annotations/models.py
index e2b292a45..bdba607f6 100644
--- a/geniza/annotations/models.py
+++ b/geniza/annotations/models.py
@@ -1,3 +1,5 @@
+import hashlib
+import json
import re
import uuid
from collections import defaultdict
@@ -193,7 +195,7 @@ def set_content(self, data):
and the data will not be saved.
"""
# remove any values tracked on the model; redundant in json field
- for val in ["id", "created", "modified", "@context", "type"]:
+ for val in ["id", "created", "modified", "@context", "type", "etag"]:
if val in data:
del data[val]
@@ -247,6 +249,14 @@ def sanitize_html(cls, html):
else:
return cleaned_html
+ @property
+ def etag(self):
+ """Compute and return an md5 hash of content to use as an ETag"""
+ # must be a string encoded as utf-8 to compute md5 hash
+ content_str = json.dumps(self.content, sort_keys=True).encode("utf-8")
+ # ETag should be wrapped in double quotes, per Django @condition docs
+ return f'"{hashlib.md5(content_str).hexdigest()}"'
+
def compile(self, include_context=True):
"""Combine annotation data and return as a dictionary that
can be serialized as JSON. Includes context by default,
@@ -258,6 +268,11 @@ def compile(self, include_context=True):
anno = {}
if include_context:
anno = {"@context": "http://www.w3.org/ns/anno.jsonld"}
+ else:
+ # NOTE: ETag required here for inclusion in annotation list, which is how
+ # annotations are fetched during editing; need to associate each ETag with
+ # an individual annotation, for comparison with response ETag on POST/DELETE
+ anno = {"etag": self.etag}
# define fields in desired order
anno.update(
diff --git a/geniza/annotations/tests/test_annotations_models.py b/geniza/annotations/tests/test_annotations_models.py
index 170ba920a..9d1c7eabb 100644
--- a/geniza/annotations/tests/test_annotations_models.py
+++ b/geniza/annotations/tests/test_annotations_models.py
@@ -18,6 +18,27 @@ def test_get_absolute_url(self):
anno = Annotation()
assert anno.get_absolute_url() == "/annotations/%s/" % anno.pk
+ def test_etag(self, annotation):
+ old_etag = annotation.etag
+ # should be surrounded by doublequotes
+ assert old_etag[0] == old_etag[-1] == '"'
+ # should be length of an md5 hash + two characters
+ assert len(old_etag) == 34
+ # changing content should change etag
+ annotation.content.update(
+ {
+ "foo": "bar",
+ "id": "bogus",
+ "created": "yesterday",
+ "modified": "today",
+ }
+ )
+ assert annotation.etag != old_etag
+ new_etag = annotation.etag
+ # changing other properties on the annotation should not change etag
+ annotation.footnote = Footnote()
+ assert annotation.etag == new_etag
+
@pytest.mark.django_db
def test_uri(self):
anno = Annotation()
@@ -31,12 +52,13 @@ def test_set_content(self, source, document):
"id": absolutize_url("/annotations/1"),
"type": "Annotation",
"foo": "bar",
+ "etag": "abcd1234",
}
anno = Annotation(footnote=footnote)
anno.set_content(content)
# check that appropriate fields were removed
- for field in ["@context", "id", "type"]:
+ for field in ["@context", "id", "type", "etag"]:
assert field not in anno.content
# remaining content was set
assert anno.content["foo"] == "bar"
@@ -122,6 +144,12 @@ def test_compile(self, annotation):
compiled = line.compile()
assert compiled["partOf"] == annotation.uri()
+ # when include_context=False (i.e. part of a list), should include etag, since
+ # we need a way to associate individual ETag to each item returned in list response
+ compiled = line.compile(include_context=False)
+ assert compiled["etag"] == line.etag
+ assert "@context" not in compiled
+
def test_sanitize_html(self):
html = '
test
line
'
# should strip out all unwanted elements and attributes (table, div, style)
diff --git a/geniza/annotations/tests/test_annotations_views.py b/geniza/annotations/tests/test_annotations_views.py
index 4b51fca10..220425c21 100644
--- a/geniza/annotations/tests/test_annotations_views.py
+++ b/geniza/annotations/tests/test_annotations_views.py
@@ -8,11 +8,61 @@
from pytest_django.asserts import assertContains, assertNotContains
from geniza.annotations.models import Annotation
-from geniza.annotations.views import AnnotationResponse
+from geniza.annotations.views import AnnotationLastModifiedMixin, AnnotationResponse
from geniza.corpus.models import Document
from geniza.footnotes.models import Footnote, Source, SourceType
+class TestAnnotationLastModifiedMixin:
+ def test_get_etag(self, annotation):
+ mixin = AnnotationLastModifiedMixin()
+ # nonexistent annotation should return None
+ assert mixin.get_etag(request=None, pk=1234) == None
+ # otherwise should return ETag
+ assert mixin.get_etag(request=None, pk=annotation.pk) == annotation.etag
+
+ def test_get_last_modified(self, annotation):
+ mixin = AnnotationLastModifiedMixin()
+ # nonexistent annotation should return None
+ assert mixin.get_last_modified(request=None, pk=1234) == None
+ # otherwise should return last modified
+ assert (
+ mixin.get_last_modified(request=None, pk=annotation.pk)
+ == annotation.modified
+ )
+
+ def test_disaptch(self, admin_client, annotation):
+ # integration test for conditional response
+ # get current etag
+ old_etag = annotation.etag
+
+ # change content
+ annotation.content = {
+ **annotation.content,
+ "body": [{"value": "changed"}],
+ }
+ annotation.save()
+
+ # attempt to update annotation with POST request as admin, including the old
+ # ETag in If-Match header
+ response = admin_client.post(
+ annotation.get_absolute_url(),
+ json.dumps(annotation.content),
+ content_type="application/json",
+ HTTP_IF_MATCH=old_etag,
+ )
+ # should result in 412 Precondition Failed status
+ assert response.status_code == 412
+
+ # attempt to delete annotation with DELETE request as admin, including the old
+ # ETag in If-Match header
+ response = admin_client.delete(
+ annotation.get_absolute_url(), HTTP_IF_MATCH=old_etag
+ )
+ # should result in 412 Precondition Failed status
+ assert response.status_code == 412
+
+
@pytest.mark.django_db
class TestAnnotationList:
anno_list_url = reverse("annotations:list")
diff --git a/geniza/annotations/views.py b/geniza/annotations/views.py
index 212eda569..7099a08b6 100644
--- a/geniza/annotations/views.py
+++ b/geniza/annotations/views.py
@@ -7,6 +7,7 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import BadRequest
from django.http import HttpResponse, JsonResponse
+from django.views.decorators.http import condition
from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import MultipleObjectMixin
@@ -30,6 +31,38 @@ class ApiAccessMixin(AccessMixin):
raise_exception = True # return an error instead of redirecting to login
+class AnnotationLastModifiedMixin(View):
+ """View mixin to add ETag/last modified headers."""
+
+ def get_etag(self, request, *args, **kwargs):
+ """Get etag from annotation"""
+ try:
+ anno = Annotation.objects.get(pk=kwargs.get("pk"))
+ return anno.etag
+ except Annotation.DoesNotExist:
+ return None
+
+ def get_last_modified(self, request, *args, **kwargs):
+ """Return last modified :class:`datetime.datetime`"""
+ try:
+ anno = Annotation.objects.get(pk=kwargs.get("pk"))
+ return anno.modified
+ except Annotation.DoesNotExist:
+ return None
+
+ def dispatch(self, request, *args, **kwargs):
+ """Wrap the dispatch method to add ETag/last modified headers when
+ appropriate, then return a conditional response."""
+
+ @condition(etag_func=self.get_etag, last_modified_func=self.get_last_modified)
+ def _dispatch(request, *args, **kwargs):
+ return super(AnnotationLastModifiedMixin, self).dispatch(
+ request, *args, **kwargs
+ )
+
+ return _dispatch(request, *args, **kwargs)
+
+
class AnnotationResponse(JsonResponse):
"""Base class for annotation responses; extends json response to set
annotation profile content type."""
@@ -269,7 +302,11 @@ def get(self, request, *args, **kwargs):
class AnnotationDetail(
- PermissionRequiredMixin, ApiAccessMixin, View, SingleObjectMixin
+ PermissionRequiredMixin,
+ AnnotationLastModifiedMixin,
+ ApiAccessMixin,
+ View,
+ SingleObjectMixin,
):
"""View to read, update, or delete a single annotation."""
@@ -293,7 +330,6 @@ def get_permission_required(self):
def post(self, request, *args, **kwargs):
"""update the annotation on POST"""
- # NOTE: should use etag / if-match
anno = self.get_object()
try:
anno_data = parse_annotation_data(request=request)
@@ -316,7 +352,6 @@ def post(self, request, *args, **kwargs):
def delete(self, request, *args, **kwargs):
"""delete the annotation on DELETE"""
- # should use etag / if-match
# deleted uuid should not be reused (relying on low likelihood of uuid collision)
anno = self.get_object()
# create log entry to document deletion *BEFORE* deleting
From b87950d79eb7fbe202bc1f97d5ed7a54bbd27e01 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Tue, 19 Mar 2024 11:36:31 -0400
Subject: [PATCH 78/97] Update tahqiq and improve alert style/behavior (#965)
---
package-lock.json | 12 ++++++------
package.json | 2 +-
sitemedia/js/controllers/alert_controller.js | 2 +-
sitemedia/scss/components/_alert.scss | 5 +++--
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 50c91679d..889cfe2a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,7 @@
"@recogito/annotorious": "^2.7.13",
"@recogito/annotorious-openseadragon": "^2.7.17",
"angle-input": "^0.0.1",
- "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#ba2990213d72059471752ba42af2bc552e387377",
+ "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
"autoprefixer": "^10.3.2",
"babel-loader": "^8.2.2",
"core-js": "^3.16.3",
@@ -2557,8 +2557,8 @@
},
"node_modules/annotorious-tahqiq": {
"version": "1.2.0",
- "resolved": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#ba2990213d72059471752ba42af2bc552e387377",
- "integrity": "sha512-1CkJZqXZhzZWxViC4ZJrpKDWTvw4rLNUbLM4s5RbLmMv09Jc8S9Oi4Vger2borhkGuTB50d2YhJUqY/zH0Z9Yg==",
+ "resolved": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#8a6f8ea5259c08215057f82df77310012a4dfb9b",
+ "integrity": "sha512-ThptpzC8GtkhW3g66D+21pQoDTZVzo5Z/Manyu+tNggvdmInXB8Pow/i6QOhh+v0152Vtm4UHFpDE/x+f5oetA==",
"license": "Apache-2.0",
"dependencies": {
"@tinymce/tinymce-webcomponent": "^2.0.0",
@@ -10912,9 +10912,9 @@
"integrity": "sha512-jq2/ZAjbS3yoICQuAqMEVty2tJdrS6GW4wRExmqHScqno41II0C/wZ0e8fQ/hJ2xUB7CSpfEcbjC/tec57o/Hg=="
},
"annotorious-tahqiq": {
- "version": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#ba2990213d72059471752ba42af2bc552e387377",
- "integrity": "sha512-1CkJZqXZhzZWxViC4ZJrpKDWTvw4rLNUbLM4s5RbLmMv09Jc8S9Oi4Vger2borhkGuTB50d2YhJUqY/zH0Z9Yg==",
- "from": "annotorious-tahqiq@github:Princeton-CDH/annotorious-tahqiq#ba2990213d72059471752ba42af2bc552e387377",
+ "version": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#8a6f8ea5259c08215057f82df77310012a4dfb9b",
+ "integrity": "sha512-ThptpzC8GtkhW3g66D+21pQoDTZVzo5Z/Manyu+tNggvdmInXB8Pow/i6QOhh+v0152Vtm4UHFpDE/x+f5oetA==",
+ "from": "annotorious-tahqiq@github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
"requires": {
"@tinymce/tinymce-webcomponent": "^2.0.0",
"@ungap/custom-elements": "^1.1.0"
diff --git a/package.json b/package.json
index 5827eb272..25f08fcfe 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"@recogito/annotorious": "^2.7.13",
"@recogito/annotorious-openseadragon": "^2.7.17",
"angle-input": "^0.0.1",
- "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#ba2990213d72059471752ba42af2bc552e387377",
+ "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
"autoprefixer": "^10.3.2",
"babel-loader": "^8.2.2",
"core-js": "^3.16.3",
diff --git a/sitemedia/js/controllers/alert_controller.js b/sitemedia/js/controllers/alert_controller.js
index 07260a3e7..c491bb2f8 100644
--- a/sitemedia/js/controllers/alert_controller.js
+++ b/sitemedia/js/controllers/alert_controller.js
@@ -27,7 +27,7 @@ export default class extends Controller {
this.element.appendChild(alert);
// longer timeout for error messages for readability
- const timeout = status === "error" ? 5000 : 3000;
+ const timeout = status === "error" ? 10000 : 3000;
// remove visible class after timeout
setTimeout(() => {
diff --git a/sitemedia/scss/components/_alert.scss b/sitemedia/scss/components/_alert.scss
index 47a365cc8..2ed5d3ff3 100644
--- a/sitemedia/scss/components/_alert.scss
+++ b/sitemedia/scss/components/_alert.scss
@@ -20,8 +20,9 @@ div#alerts {
justify-content: center;
position: relative;
min-width: 20rem;
- height: 1rem;
- padding: 1.25rem;
+ max-width: 50rem;
+ min-height: 1rem;
+ padding: 0.25rem 1rem;
margin-bottom: 0.25rem;
background-color: var(--background-gray);
color: var(--on-background-light);
From a85eaa2f334f7cc0dab72731e9693188ddeddb5d Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 21 Mar 2024 13:14:41 -0400
Subject: [PATCH 79/97] Use through models with notes field for event relations
(#1512)
---
geniza/corpus/admin.py | 7 ++-
.../corpus/migrations/0046_document_events.py | 32 +++++++++-
geniza/corpus/models.py | 16 ++++-
geniza/corpus/tests/test_corpus_models.py | 10 ++++
geniza/entities/admin.py | 26 +++++---
geniza/entities/forms.py | 17 +++---
geniza/entities/migrations/0021_event.py | 60 ++++++++++++++++++-
.../migrations/0022_event_permissions.py | 12 ++++
geniza/entities/models.py | 32 +++++++++-
geniza/entities/tests/test_entities_models.py | 22 +++++++
10 files changed, 214 insertions(+), 20 deletions(-)
diff --git a/geniza/corpus/admin.py b/geniza/corpus/admin.py
index 656e9ab05..041768e58 100644
--- a/geniza/corpus/admin.py
+++ b/geniza/corpus/admin.py
@@ -25,6 +25,7 @@
Collection,
Dating,
Document,
+ DocumentEventRelation,
DocumentType,
Fragment,
LanguageScript,
@@ -379,12 +380,16 @@ class DocumentEventInline(admin.TabularInline):
"""Inline for events related to a document"""
autocomplete_fields = ("event",)
- model = Document.events.through
+ fields = ("event", "notes")
+ model = DocumentEventRelation
min_num = 0
extra = 1
show_change_link = True
verbose_name = "Related Event"
verbose_name_plural = "Related Events"
+ formfield_overrides = {
+ TextField: {"widget": Textarea(attrs={"rows": "4"})},
+ }
@admin.register(Document)
diff --git a/geniza/corpus/migrations/0046_document_events.py b/geniza/corpus/migrations/0046_document_events.py
index 04ccc7969..a0e3fb69a 100644
--- a/geniza/corpus/migrations/0046_document_events.py
+++ b/geniza/corpus/migrations/0046_document_events.py
@@ -1,5 +1,6 @@
-# Generated by Django 3.2.23 on 2024-03-13 21:09
+# Generated by Django 3.2.23 on 2024-03-21 16:41
+import django.db.models.deletion
from django.db import migrations, models
@@ -10,11 +11,40 @@ class Migration(migrations.Migration):
]
operations = [
+ migrations.CreateModel(
+ name="DocumentEventRelation",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("notes", models.TextField(blank=True)),
+ (
+ "document",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="corpus.document",
+ ),
+ ),
+ (
+ "event",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="entities.event"
+ ),
+ ),
+ ],
+ ),
migrations.AddField(
model_name="document",
name="events",
field=models.ManyToManyField(
related_name="documents",
+ through="corpus.DocumentEventRelation",
to="entities.Event",
verbose_name="Related Events",
),
diff --git a/geniza/corpus/models.py b/geniza/corpus/models.py
index 98ad47e83..0c244e94d 100644
--- a/geniza/corpus/models.py
+++ b/geniza/corpus/models.py
@@ -563,7 +563,10 @@ class Document(ModelIndexable, DocumentDateMixin):
help_text="Decide whether a document should be publicly visible",
)
events = models.ManyToManyField(
- to="entities.Event", related_name="documents", verbose_name="Related Events"
+ to="entities.Event",
+ related_name="documents",
+ verbose_name="Related Events",
+ through="DocumentEventRelation",
)
footnotes = GenericRelation(Footnote, related_query_name="document")
log_entries = GenericRelation(LogEntry, related_query_name="document")
@@ -1725,3 +1728,14 @@ class Meta:
def standard_date_display(self):
"""Standard date in human-readable format for document details pages"""
return Document.standard_date_display(self.standard_date)
+
+
+class DocumentEventRelation(models.Model):
+ """A relationship between a document and an event"""
+
+ document = models.ForeignKey(Document, on_delete=models.CASCADE)
+ event = models.ForeignKey("entities.Event", on_delete=models.CASCADE)
+ notes = models.TextField(blank=True)
+
+ def __str__(self):
+ return f"Document-Event relation: {self.document} and {self.event}"
diff --git a/geniza/corpus/tests/test_corpus_models.py b/geniza/corpus/tests/test_corpus_models.py
index 1654d4bcb..303869736 100644
--- a/geniza/corpus/tests/test_corpus_models.py
+++ b/geniza/corpus/tests/test_corpus_models.py
@@ -28,11 +28,13 @@
Collection,
Dating,
Document,
+ DocumentEventRelation,
DocumentType,
Fragment,
LanguageScript,
TextBlock,
)
+from geniza.entities.models import Event
from geniza.footnotes.models import Footnote, Source, SourceLanguage, SourceType
@@ -1990,3 +1992,11 @@ def test_items_to_index(document, footnote):
docs = Document.items_to_index()
assert docs
assert isinstance(docs, MultilingualQuerySet)
+
+
+@pytest.mark.django_db
+class TestDocumentEventRelation:
+ def test_str(self, document):
+ event = Event.objects.create(name="Founding of the Ben Ezra Synagogue")
+ relation = DocumentEventRelation.objects.create(document=document, event=event)
+ assert str(relation) == f"Document-Event relation: {document} and {event}"
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index b7f0c0b31..2194883e9 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -13,6 +13,7 @@
from modeltranslation.admin import TabbedTranslationAdmin
from geniza.corpus.dates import DocumentDateMixin
+from geniza.corpus.models import DocumentEventRelation
from geniza.entities.forms import (
EventForm,
EventPersonForm,
@@ -30,12 +31,14 @@
Person,
PersonDocumentRelation,
PersonDocumentRelationType,
+ PersonEventRelation,
PersonPersonRelation,
PersonPersonRelationType,
PersonPlaceRelation,
PersonPlaceRelationType,
PersonRole,
Place,
+ PlaceEventRelation,
PlacePlaceRelation,
PlacePlaceRelationType,
)
@@ -223,12 +226,16 @@ class PersonEventInline(admin.TabularInline):
"""Inline for events related to a person"""
autocomplete_fields = ("event",)
- model = Person.events.through
+ fields = ("event", "notes")
+ model = PersonEventRelation
min_num = 0
extra = 1
show_change_link = True
verbose_name = "Related Event"
verbose_name_plural = "Related Events"
+ formfield_overrides = {
+ TextField: {"widget": Textarea(attrs={"rows": "4"})},
+ }
@admin.register(Person)
@@ -409,12 +416,16 @@ class PlaceEventInline(admin.TabularInline):
"""Inline for events related to a place"""
autocomplete_fields = ("event",)
- model = Place.events.through
+ fields = ("event", "notes")
+ model = PlaceEventRelation
min_num = 0
extra = 1
show_change_link = True
verbose_name = "Related Event"
verbose_name_plural = "Related Events"
+ formfield_overrides = {
+ TextField: {"widget": Textarea(attrs={"rows": "4"})},
+ }
@admin.register(Place)
@@ -456,30 +467,31 @@ class PlacePlaceRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
class EventDocumentInline(DocumentInline):
"""Related documents inline for the Event admin"""
- model = Event.documents.through
+ model = DocumentEventRelation
autocomplete_fields = ("document",)
fields = (
"document",
"document_description",
+ "notes",
)
class EventPersonInline(PersonInline):
"""Related people inline for the Event admin"""
- model = Event.people.through
+ model = PersonEventRelation
form = EventPersonForm
autocomplete_fields = ("person",)
- fields = ("person",)
+ fields = ("person", "notes")
class EventPlaceInline(PlaceInline):
"""Related places inline for the Event admin"""
- model = Event.places.through
+ model = PlaceEventRelation
form = EventPlaceForm
autocomplete_fields = ("place",)
- fields = ("place",)
+ fields = ("place", "notes")
@admin.register(Event)
diff --git a/geniza/entities/forms.py b/geniza/entities/forms.py
index 257177a6f..a065e7e9e 100644
--- a/geniza/entities/forms.py
+++ b/geniza/entities/forms.py
@@ -3,10 +3,11 @@
from django.template.loader import get_template
from geniza.entities.models import (
- Event,
Person,
+ PersonEventRelation,
PersonPersonRelation,
PersonPlaceRelation,
+ PlaceEventRelation,
PlacePlaceRelation,
)
@@ -109,19 +110,21 @@ class Meta:
class EventPersonForm(forms.ModelForm):
class Meta:
- model = Event.people.through
- fields = ("person",)
+ model = PersonEventRelation
+ fields = ("person", "notes")
widgets = {
- "person": autocomplete.ModelSelect2(url="entities:person-autocomplete")
+ "person": autocomplete.ModelSelect2(url="entities:person-autocomplete"),
+ "notes": forms.Textarea(attrs={"rows": "4"}),
}
class EventPlaceForm(forms.ModelForm):
class Meta:
- model = Event.places.through
- fields = ("place",)
+ model = PlaceEventRelation
+ fields = ("place", "notes")
widgets = {
- "place": autocomplete.ModelSelect2(url="entities:place-autocomplete")
+ "place": autocomplete.ModelSelect2(url="entities:place-autocomplete"),
+ "notes": forms.Textarea(attrs={"rows": "4"}),
}
diff --git a/geniza/entities/migrations/0021_event.py b/geniza/entities/migrations/0021_event.py
index 676c70f75..9225223c7 100644
--- a/geniza/entities/migrations/0021_event.py
+++ b/geniza/entities/migrations/0021_event.py
@@ -1,8 +1,9 @@
-# Generated by Django 3.2.23 on 2024-03-13 21:09
+# Generated by Django 3.2.23 on 2024-03-21 16:41
import re
import django.core.validators
+import django.db.models.deletion
from django.db import migrations, models
@@ -107,11 +108,67 @@ class Migration(migrations.Migration):
),
],
),
+ migrations.CreateModel(
+ name="PlaceEventRelation",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("notes", models.TextField(blank=True)),
+ (
+ "event",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="entities.event"
+ ),
+ ),
+ (
+ "place",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="entities.place"
+ ),
+ ),
+ ],
+ ),
+ migrations.CreateModel(
+ name="PersonEventRelation",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("notes", models.TextField(blank=True)),
+ (
+ "event",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="entities.event"
+ ),
+ ),
+ (
+ "person",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="entities.person",
+ ),
+ ),
+ ],
+ ),
migrations.AddField(
model_name="person",
name="events",
field=models.ManyToManyField(
related_name="people",
+ through="entities.PersonEventRelation",
to="entities.Event",
verbose_name="Related Events",
),
@@ -121,6 +178,7 @@ class Migration(migrations.Migration):
name="events",
field=models.ManyToManyField(
related_name="places",
+ through="entities.PlaceEventRelation",
to="entities.Event",
verbose_name="Related Events",
),
diff --git a/geniza/entities/migrations/0022_event_permissions.py b/geniza/entities/migrations/0022_event_permissions.py
index 507776eab..6ac3ec595 100644
--- a/geniza/entities/migrations/0022_event_permissions.py
+++ b/geniza/entities/migrations/0022_event_permissions.py
@@ -11,6 +11,18 @@
"change_event",
"delete_event",
"view_event",
+ "add_documenteventrelation",
+ "change_documenteventrelation",
+ "delete_documenteventrelation",
+ "view_documenteventrelation",
+ "add_personeventrelation",
+ "change_personeventrelation",
+ "delete_personeventrelation",
+ "view_personeventrelation",
+ "add_placeeventrelation",
+ "change_placeeventrelation",
+ "delete_placeeventrelation",
+ "view_placeeventrelation",
]
diff --git a/geniza/entities/models.py b/geniza/entities/models.py
index 78f17dbab..c41266a8e 100644
--- a/geniza/entities/models.py
+++ b/geniza/entities/models.py
@@ -206,7 +206,10 @@ class Person(models.Model):
help_text="Social role",
)
events = models.ManyToManyField(
- Event, related_name="people", verbose_name="Related Events"
+ Event,
+ related_name="people",
+ verbose_name="Related Events",
+ through="PersonEventRelation",
)
# sources for the information gathered here
@@ -550,6 +553,17 @@ def __str__(self):
return f"{relation_type} relation: {self.to_person} and {self.from_person}"
+class PersonEventRelation(models.Model):
+ """A relationship between a person and an event"""
+
+ person = models.ForeignKey(Person, on_delete=models.CASCADE)
+ event = models.ForeignKey(Event, on_delete=models.CASCADE)
+ notes = models.TextField(blank=True)
+
+ def __str__(self):
+ return f"Person-Event relation: {self.person} and {self.event}"
+
+
class Place(models.Model):
"""A named geographical location, which may be associated with documents or people."""
@@ -574,7 +588,10 @@ class Place(models.Model):
)
notes = models.TextField(blank=True)
events = models.ManyToManyField(
- Event, related_name="places", verbose_name="Related Events"
+ Event,
+ related_name="places",
+ verbose_name="Related Events",
+ through="PlaceEventRelation",
)
# sources for the information gathered here
footnotes = GenericRelation(Footnote, blank=True, related_name="places")
@@ -731,3 +748,14 @@ class Meta:
def __str__(self):
return f"{self.type} relation: {self.place_a} and {self.place_b}"
+
+
+class PlaceEventRelation(models.Model):
+ """A relationship between a place and an event"""
+
+ place = models.ForeignKey(Place, on_delete=models.CASCADE)
+ event = models.ForeignKey(Event, on_delete=models.CASCADE)
+ notes = models.TextField(blank=True)
+
+ def __str__(self):
+ return f"Place-Event relation: {self.place} and {self.event}"
diff --git a/geniza/entities/tests/test_entities_models.py b/geniza/entities/tests/test_entities_models.py
index 7dfc92ae1..9a6ee1a1b 100644
--- a/geniza/entities/tests/test_entities_models.py
+++ b/geniza/entities/tests/test_entities_models.py
@@ -16,12 +16,14 @@
Person,
PersonDocumentRelation,
PersonDocumentRelationType,
+ PersonEventRelation,
PersonPersonRelation,
PersonPersonRelationType,
PersonPlaceRelation,
PersonPlaceRelationType,
PersonRole,
Place,
+ PlaceEventRelation,
PlacePlaceRelation,
PlacePlaceRelationType,
)
@@ -490,3 +492,23 @@ def test_documents_date_range(self, document, join):
join.doc_date_standard = "1000/1010"
join.save()
assert event.documents_date_range == "1000/1150"
+
+
+@pytest.mark.django_db
+class TestPersonEventRelation:
+ def test_str(self):
+ goitein = Person.objects.create()
+ Name.objects.create(name="Goitein", content_object=goitein)
+ event = Event.objects.create(name="S.D. Goitein's first publication")
+ relation = PersonEventRelation.objects.create(person=goitein, event=event)
+ assert str(relation) == f"Person-Event relation: {goitein} and {event}"
+
+
+@pytest.mark.django_db
+class TestPlaceEventRelation:
+ def test_str(self):
+ fustat = Place.objects.create()
+ Name.objects.create(name="Fustat", content_object=fustat)
+ event = Event.objects.create(name="Founding of the Ben Ezra Synagogue")
+ relation = PlaceEventRelation.objects.create(place=fustat, event=event)
+ assert str(relation) == f"Place-Event relation: {fustat} and {event}"
From 7f0a1c224246e97decce2588bd16f23b9b6080f4 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 21 Mar 2024 14:52:26 -0400
Subject: [PATCH 80/97] Require at least one document when adding event (#1512)
---
geniza/entities/admin.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index 2194883e9..74d9938f1 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -474,6 +474,8 @@ class EventDocumentInline(DocumentInline):
"document_description",
"notes",
)
+ min_num = 1
+ extra = 0
class EventPersonInline(PersonInline):
From 0855fc4c47814bb5f2945f20a20b7f4595c7fb4a Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 21 Mar 2024 15:25:38 -0400
Subject: [PATCH 81/97] Only allow adding events from document or event admins
(#1512) Also fix behavior with min_num for document event relations
---
geniza/entities/admin.py | 27 +++++++++++++++++++-
geniza/entities/tests/test_entities_admin.py | 20 +++++++++++++++
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index 74d9938f1..58fc2c466 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -237,6 +237,14 @@ class PersonEventInline(admin.TabularInline):
TextField: {"widget": Textarea(attrs={"rows": "4"})},
}
+ def get_formset(self, request, obj=None, **kwargs):
+ """Disable the 'add' link for an Event from a Person. Must be added from
+ a document or created manually with a document attached in the admin."""
+ formset = super().get_formset(request, obj, **kwargs)
+ service = formset.form.base_fields["event"]
+ service.widget.can_add_related = False
+ return formset
+
@admin.register(Person)
class PersonAdmin(TabbedTranslationAdmin, SortableAdminBase, admin.ModelAdmin):
@@ -427,6 +435,14 @@ class PlaceEventInline(admin.TabularInline):
TextField: {"widget": Textarea(attrs={"rows": "4"})},
}
+ def get_formset(self, request, obj=None, **kwargs):
+ """Disable the 'add' link for an Event from a Place. Must be added from
+ a document or created manually with a document attached in the admin."""
+ formset = super().get_formset(request, obj, **kwargs)
+ service = formset.form.base_fields["event"]
+ service.widget.can_add_related = False
+ return formset
+
@admin.register(Place)
class PlaceAdmin(SortableAdminBase, admin.ModelAdmin):
@@ -474,9 +490,18 @@ class EventDocumentInline(DocumentInline):
"document_description",
"notes",
)
- min_num = 1
extra = 0
+ def get_min_num(self, request, obj=None, **kwargs):
+ """Override min_num to change it conditionally based on how Event is being added"""
+ if "_popup" in request.GET and request.GET["_popup"] == "1" and obj is None:
+ # If accessed via popup, min number of associated documents should be 0
+ # since it's being added via a document, so it will always get one
+ return 0
+ else:
+ # If accessed via Event section of admin, requires minimum 1 document
+ return 1
+
class EventPersonInline(PersonInline):
"""Related people inline for the Event admin"""
diff --git a/geniza/entities/tests/test_entities_admin.py b/geniza/entities/tests/test_entities_admin.py
index 73539ddcc..ac346d162 100644
--- a/geniza/entities/tests/test_entities_admin.py
+++ b/geniza/entities/tests/test_entities_admin.py
@@ -253,3 +253,23 @@ def test_iter(self):
assert len(choices[extended_family]) == 2
assert (type_a.pk, type_a.name) in choices[immediate_family]
assert (type_a.pk, type_a.name) not in choices[extended_family]
+
+
+class TestPersonEventInline:
+ def test_get_formset(self, admin_client):
+ # there should be no link to a popup to add an event from the Person admin
+ url = reverse("admin:entities_person_add")
+ response = admin_client.get(url)
+ content = str(response.content)
+ # NOTE: confirmed the following assertion fails when get_formset not overridden
+ assert "Add another event" not in content
+
+
+class TestPlaceEventInline:
+ def test_get_formset(self, admin_client):
+ # there should be no link to a popup to add an event from the Person admin
+ url = reverse("admin:entities_place_add")
+ response = admin_client.get(url)
+ content = str(response.content)
+ # NOTE: confirmed the following assertion fails when get_formset not overridden
+ assert "Add another event" not in content
From 447f717895f00a216305aa55e0ee60ff06d143ce Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 21 Mar 2024 16:09:12 -0400
Subject: [PATCH 82/97] Add unit tests for get_min_num (#1512)
---
geniza/entities/tests/test_entities_admin.py | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/geniza/entities/tests/test_entities_admin.py b/geniza/entities/tests/test_entities_admin.py
index ac346d162..669ba35ff 100644
--- a/geniza/entities/tests/test_entities_admin.py
+++ b/geniza/entities/tests/test_entities_admin.py
@@ -9,6 +9,7 @@
from geniza.corpus.models import Document, LanguageScript
from geniza.entities.admin import (
+ EventDocumentInline,
NameInlineFormSet,
PersonAdmin,
PersonDocumentInline,
@@ -273,3 +274,19 @@ def test_get_formset(self, admin_client):
content = str(response.content)
# NOTE: confirmed the following assertion fails when get_formset not overridden
assert "Add another event" not in content
+
+
+class TestEventDocumentInline:
+ def test_get_min_num(self, admin_client, document):
+ # it should be required to add at least one document from the Event admin
+ response = admin_client.get(reverse("admin:entities_event_add"))
+ content = str(response.content)
+ assert 'name="documenteventrelation_set-MIN_NUM_FORMS" value="1"' in content
+
+ # however, when accessed via popup from the Document admin, this requirement
+ # should be removed
+ response = admin_client.get(
+ reverse("admin:entities_event_add"), {"_popup": "1"}
+ )
+ content = str(response.content)
+ assert 'name="documenteventrelation_set-MIN_NUM_FORMS" value="0"' in content
From 3047ec7c57132c9583aeaf609c3bca4cafa35daa Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Mon, 25 Mar 2024 11:16:19 -0400
Subject: [PATCH 83/97] Fix typo in test method name (#965)
Co-authored-by: Rebecca Sutton Koeser
---
geniza/annotations/tests/test_annotations_views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geniza/annotations/tests/test_annotations_views.py b/geniza/annotations/tests/test_annotations_views.py
index 220425c21..e8a6c4c7d 100644
--- a/geniza/annotations/tests/test_annotations_views.py
+++ b/geniza/annotations/tests/test_annotations_views.py
@@ -31,7 +31,7 @@ def test_get_last_modified(self, annotation):
== annotation.modified
)
- def test_disaptch(self, admin_client, annotation):
+ def test_dispatch(self, admin_client, annotation):
# integration test for conditional response
# get current etag
old_etag = annotation.etag
From 4955e29bf4393ee812e2c0d66277f8e1afa0fcca Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Mon, 25 Mar 2024 11:38:30 -0400
Subject: [PATCH 84/97] Add note about content hash (#965)
---
geniza/annotations/models.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/geniza/annotations/models.py b/geniza/annotations/models.py
index bdba607f6..e893509fc 100644
--- a/geniza/annotations/models.py
+++ b/geniza/annotations/models.py
@@ -251,7 +251,11 @@ def sanitize_html(cls, html):
@property
def etag(self):
- """Compute and return an md5 hash of content to use as an ETag"""
+ """Compute and return an md5 hash of content to use as an ETag.
+
+ NOTE: Only :attr:`content` can be modified in the editor, so it is the only hashed
+ attribute. If other attributes become mutable, modify this function to include them in
+ the ETag computation."""
# must be a string encoded as utf-8 to compute md5 hash
content_str = json.dumps(self.content, sort_keys=True).encode("utf-8")
# ETag should be wrapped in double quotes, per Django @condition docs
From 993ce7e8ef45a8b2d3f0c43087d5acf3f7838dad Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Mon, 25 Mar 2024 12:22:45 -0400
Subject: [PATCH 85/97] Move last modified mixin into view; use get_object
(#965)
---
.../tests/test_annotations_views.py | 101 +++++++++---------
geniza/annotations/views.py | 65 ++++++-----
2 files changed, 81 insertions(+), 85 deletions(-)
diff --git a/geniza/annotations/tests/test_annotations_views.py b/geniza/annotations/tests/test_annotations_views.py
index e8a6c4c7d..88df624d3 100644
--- a/geniza/annotations/tests/test_annotations_views.py
+++ b/geniza/annotations/tests/test_annotations_views.py
@@ -8,61 +8,11 @@
from pytest_django.asserts import assertContains, assertNotContains
from geniza.annotations.models import Annotation
-from geniza.annotations.views import AnnotationLastModifiedMixin, AnnotationResponse
+from geniza.annotations.views import AnnotationDetail, AnnotationResponse
from geniza.corpus.models import Document
from geniza.footnotes.models import Footnote, Source, SourceType
-class TestAnnotationLastModifiedMixin:
- def test_get_etag(self, annotation):
- mixin = AnnotationLastModifiedMixin()
- # nonexistent annotation should return None
- assert mixin.get_etag(request=None, pk=1234) == None
- # otherwise should return ETag
- assert mixin.get_etag(request=None, pk=annotation.pk) == annotation.etag
-
- def test_get_last_modified(self, annotation):
- mixin = AnnotationLastModifiedMixin()
- # nonexistent annotation should return None
- assert mixin.get_last_modified(request=None, pk=1234) == None
- # otherwise should return last modified
- assert (
- mixin.get_last_modified(request=None, pk=annotation.pk)
- == annotation.modified
- )
-
- def test_dispatch(self, admin_client, annotation):
- # integration test for conditional response
- # get current etag
- old_etag = annotation.etag
-
- # change content
- annotation.content = {
- **annotation.content,
- "body": [{"value": "changed"}],
- }
- annotation.save()
-
- # attempt to update annotation with POST request as admin, including the old
- # ETag in If-Match header
- response = admin_client.post(
- annotation.get_absolute_url(),
- json.dumps(annotation.content),
- content_type="application/json",
- HTTP_IF_MATCH=old_etag,
- )
- # should result in 412 Precondition Failed status
- assert response.status_code == 412
-
- # attempt to delete annotation with DELETE request as admin, including the old
- # ETag in If-Match header
- response = admin_client.delete(
- annotation.get_absolute_url(), HTTP_IF_MATCH=old_etag
- )
- # should result in 412 Precondition Failed status
- assert response.status_code == 412
-
-
@pytest.mark.django_db
class TestAnnotationList:
anno_list_url = reverse("annotations:list")
@@ -412,6 +362,55 @@ def test_corresponding_footnote_location(self, admin_client, document):
# should have its location copied from the existing Edition footnote
assert created_digital_edition.location == "doc. 123"
+ def test_get_etag(self, annotation):
+ # nonexistent annotation should return None
+ view = AnnotationDetail()
+ view.kwargs = {"pk": 1234}
+ assert view.get_etag(request=None) == None
+ # otherwise should return ETag
+ view.kwargs = {"pk": annotation.pk}
+ assert view.get_etag(request=None) == annotation.etag
+
+ def test_get_last_modified(self, annotation):
+ view = AnnotationDetail()
+ view.kwargs = {"pk": 1234}
+ # nonexistent annotation should return None
+ assert view.get_last_modified(request=None) == None
+ # otherwise should return last modified
+ view.kwargs = {"pk": annotation.pk}
+ assert view.get_last_modified(request=None) == annotation.modified
+
+ def test_dispatch(self, admin_client, annotation):
+ # integration test for conditional response
+ # get current etag
+ old_etag = annotation.etag
+
+ # change content
+ annotation.content = {
+ **annotation.content,
+ "body": [{"value": "changed"}],
+ }
+ annotation.save()
+
+ # attempt to update annotation with POST request as admin, including the old
+ # ETag in If-Match header
+ response = admin_client.post(
+ annotation.get_absolute_url(),
+ json.dumps(annotation.content),
+ content_type="application/json",
+ HTTP_IF_MATCH=old_etag,
+ )
+ # should result in 412 Precondition Failed status
+ assert response.status_code == 412
+
+ # attempt to delete annotation with DELETE request as admin, including the old
+ # ETag in If-Match header
+ response = admin_client.delete(
+ annotation.get_absolute_url(), HTTP_IF_MATCH=old_etag
+ )
+ # should result in 412 Precondition Failed status
+ assert response.status_code == 412
+
@pytest.mark.django_db
class TestAnnotationSearch:
diff --git a/geniza/annotations/views.py b/geniza/annotations/views.py
index 7099a08b6..b13ed7dfe 100644
--- a/geniza/annotations/views.py
+++ b/geniza/annotations/views.py
@@ -6,7 +6,7 @@
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import BadRequest
-from django.http import HttpResponse, JsonResponse
+from django.http import Http404, HttpResponse, JsonResponse
from django.views.decorators.http import condition
from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
@@ -31,38 +31,6 @@ class ApiAccessMixin(AccessMixin):
raise_exception = True # return an error instead of redirecting to login
-class AnnotationLastModifiedMixin(View):
- """View mixin to add ETag/last modified headers."""
-
- def get_etag(self, request, *args, **kwargs):
- """Get etag from annotation"""
- try:
- anno = Annotation.objects.get(pk=kwargs.get("pk"))
- return anno.etag
- except Annotation.DoesNotExist:
- return None
-
- def get_last_modified(self, request, *args, **kwargs):
- """Return last modified :class:`datetime.datetime`"""
- try:
- anno = Annotation.objects.get(pk=kwargs.get("pk"))
- return anno.modified
- except Annotation.DoesNotExist:
- return None
-
- def dispatch(self, request, *args, **kwargs):
- """Wrap the dispatch method to add ETag/last modified headers when
- appropriate, then return a conditional response."""
-
- @condition(etag_func=self.get_etag, last_modified_func=self.get_last_modified)
- def _dispatch(request, *args, **kwargs):
- return super(AnnotationLastModifiedMixin, self).dispatch(
- request, *args, **kwargs
- )
-
- return _dispatch(request, *args, **kwargs)
-
-
class AnnotationResponse(JsonResponse):
"""Base class for annotation responses; extends json response to set
annotation profile content type."""
@@ -303,7 +271,6 @@ def get(self, request, *args, **kwargs):
class AnnotationDetail(
PermissionRequiredMixin,
- AnnotationLastModifiedMixin,
ApiAccessMixin,
View,
SingleObjectMixin,
@@ -387,3 +354,33 @@ def delete(self, request, *args, **kwargs):
footnote.refresh_from_db()
return HttpResponse(status=204)
+
+ def get_etag(self, request, *args, **kwargs):
+ """Get etag from annotation"""
+ try:
+ if not hasattr(self, "object"):
+ self.object = self.get_object()
+ anno = self.object
+ return anno.etag
+ except Http404:
+ return None
+
+ def get_last_modified(self, request, *args, **kwargs):
+ """Return last modified :class:`datetime.datetime`"""
+ try:
+ if not hasattr(self, "object"):
+ self.object = self.get_object()
+ anno = self.object
+ return anno.modified
+ except Http404:
+ return None
+
+ def dispatch(self, request, *args, **kwargs):
+ """Wrap the dispatch method to add ETag/last modified headers when
+ appropriate, then return a conditional response."""
+
+ @condition(etag_func=self.get_etag, last_modified_func=self.get_last_modified)
+ def _dispatch(request, *args, **kwargs):
+ return super(AnnotationDetail, self).dispatch(request, *args, **kwargs)
+
+ return _dispatch(request, *args, **kwargs)
From 9d5d4003d091ad6ad46734e79afde3ae9e28e330 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Mon, 25 Mar 2024 15:40:09 -0400
Subject: [PATCH 86/97] hotfix: Fix bug with booleans combined with exact
matches
---
geniza/corpus/solr_queryset.py | 28 +++++++++++++------
.../corpus/tests/test_corpus_solrqueryset.py | 6 ++++
2 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/geniza/corpus/solr_queryset.py b/geniza/corpus/solr_queryset.py
index d220894c0..8f4fef288 100644
--- a/geniza/corpus/solr_queryset.py
+++ b/geniza/corpus/solr_queryset.py
@@ -1,3 +1,4 @@
+import itertools
import re
from bs4 import BeautifulSoup
@@ -108,7 +109,7 @@ class DocumentSolrQuerySet(AliasedSolrQuerySet):
# beginning/end of the string or after/before a space, and not followed by a
# tilde for fuzzy/proximity search (non-greedy to prevent matching the entire
# string if there are multiple sets of doublequotes)
- re_exact_match = re.compile(r'(?
Date: Fri, 29 Mar 2024 12:10:52 -0400
Subject: [PATCH 87/97] Add clarifying comments for get_min_num (#1512)
---
geniza/entities/admin.py | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index 58fc2c466..701da3e4f 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -493,13 +493,19 @@ class EventDocumentInline(DocumentInline):
extra = 0
def get_min_num(self, request, obj=None, **kwargs):
- """Override min_num to change it conditionally based on how Event is being added"""
+ """On new Event creation, set min_num of Document relationships conditionally based on
+ whether it is being created from a popup in the admin edit page for a Document, or
+ created from the Event admin"""
if "_popup" in request.GET and request.GET["_popup"] == "1" and obj is None:
- # If accessed via popup, min number of associated documents should be 0
- # since it's being added via a document, so it will always get one
+ # For admin convenience: If a new Event is being created (via popup) in the Document
+ # admin, min number of associated documents should be 0; otherwise admins would have
+ # to create the relationship manually from within the popup even though it is about
+ # to be created by saving the Document.
+ # NOTE: If an Event is created in the Document admin and the Document is NOT saved,
+ # or the relationship is removed before saving, an orphan Event could be created.
return 0
else:
- # If accessed via Event section of admin, requires minimum 1 document
+ # If accessed via Event section of admin, requires minimum 1 related Document.
return 1
From 22681a37d42e4232e8f13d7b9b9f82ccc9e18bd0 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 29 Mar 2024 12:29:20 -0400
Subject: [PATCH 88/97] Improve docstring for Event date string (#1512)
---
geniza/entities/models.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/geniza/entities/models.py b/geniza/entities/models.py
index c41266a8e..815fff470 100644
--- a/geniza/entities/models.py
+++ b/geniza/entities/models.py
@@ -105,7 +105,10 @@ def __str__(self):
@property
def date_str(self):
- """Return a formatted string for the event's date/range, for use in public site"""
+ """Return a formatted string for the event's date/range, for use in public site.
+ Display date override takes highest precedence; fallback to formatted CE date override,
+ then computed date range from associated documents if neither override is set.
+ """
return (
self.display_date
or Document.standard_date_display(self.standard_date)
From 351d4c6856fcdcfff62ac3ec391b3245a825cb1a Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 29 Mar 2024 12:37:09 -0400
Subject: [PATCH 89/97] Pull out standard_date_display into utility method
(#1512)
---
geniza/corpus/admin.py | 4 +-
geniza/corpus/dates.py | 42 ++++++-------
geniza/corpus/models.py | 4 +-
geniza/corpus/tests/test_dates.py | 61 +++++++++----------
geniza/entities/admin.py | 4 +-
geniza/entities/models.py | 6 +-
geniza/entities/tests/test_entities_models.py | 7 +--
7 files changed, 63 insertions(+), 65 deletions(-)
diff --git a/geniza/corpus/admin.py b/geniza/corpus/admin.py
index 6bbcda68e..17308e437 100644
--- a/geniza/corpus/admin.py
+++ b/geniza/corpus/admin.py
@@ -18,7 +18,7 @@
from geniza.annotations.models import Annotation
from geniza.common.admin import custom_empty_field_list_filter
-from geniza.corpus.dates import DocumentDateMixin
+from geniza.corpus.dates import DocumentDateMixin, standard_date_display
from geniza.corpus.forms import DocumentPersonForm, DocumentPlaceForm
from geniza.corpus.metadata_export import AdminDocumentExporter, AdminFragmentExporter
from geniza.corpus.models import (
@@ -441,7 +441,7 @@ def view_old_pgpids(self, obj):
description="Standard date",
)
def standard_date(self, obj):
- return Document.standard_date_display(obj.doc_date_standard)
+ return standard_date_display(obj.doc_date_standard)
list_filter = (
"doctype",
diff --git a/geniza/corpus/dates.py b/geniza/corpus/dates.py
index bd2f9a2c8..9774dea92 100644
--- a/geniza/corpus/dates.py
+++ b/geniza/corpus/dates.py
@@ -183,31 +183,11 @@ def original_date(self):
[self.doc_date_original, self.get_doc_date_calendar_display()]
).strip()
- @staticmethod
- def standard_date_display(standard_date):
- """Display standard date in human readable format, when set."""
- # bail out if there is nothing to display
- if not standard_date:
- return
-
- # currently storing in isoformat, with slash if a date range
- dates = standard_date.split("/")
- # we should always have at least one date, if date is set
- # convert to local partial date object for precision-aware string formatting
- # join dates with en-dash if more than one;
- # add CE to the end to make calendar system explicit
- try:
- return "%s CE" % " – ".join(str(PartialDate(d)) for d in dates)
- except ValueError:
- # dates entered before validation was applied may not parse
- # as fallback, display as is
- return "%s CE" % standard_date
-
@property
def document_date(self):
"""Generate formatted display of combined original and standardized dates"""
if self.doc_date_standard:
- standardized_date = self.standard_date_display(self.doc_date_standard)
+ standardized_date = standard_date_display(self.doc_date_standard)
# add parentheses to standardized date if original date is also present
if self.original_date:
# NOTE: we want no-wrap for individual dates when displaying as html
@@ -528,3 +508,23 @@ def get_islamic_month(month_name):
with or without accents, and supports local month-name overrides."""
month_name = unidecode(month_name)
return islamic_months.index(islamic_month_aliases.get(month_name, month_name)) + 1
+
+
+def standard_date_display(standard_date):
+ """Display a standardized CE date in human readable format."""
+ # bail out if there is nothing to display
+ if not standard_date:
+ return
+
+ # currently storing in isoformat, with slash if a date range
+ dates = standard_date.split("/")
+ # we should always have at least one date, if date is set
+ # convert to local partial date object for precision-aware string formatting
+ # join dates with en-dash if more than one;
+ # add CE to the end to make calendar system explicit
+ try:
+ return "%s CE" % " – ".join(str(PartialDate(d)) for d in dates)
+ except ValueError:
+ # dates entered before validation was applied may not parse
+ # as fallback, display as is
+ return "%s CE" % standard_date
diff --git a/geniza/corpus/models.py b/geniza/corpus/models.py
index afcd07b6d..943f67ee1 100644
--- a/geniza/corpus/models.py
+++ b/geniza/corpus/models.py
@@ -44,7 +44,7 @@
)
from geniza.common.utils import absolutize_url
from geniza.corpus.annotation_utils import document_id_from_manifest_uri
-from geniza.corpus.dates import DocumentDateMixin, PartialDate
+from geniza.corpus.dates import DocumentDateMixin, PartialDate, standard_date_display
from geniza.corpus.iiif_utils import GenizaManifestImporter, get_iiif_string
from geniza.corpus.solr_queryset import DocumentSolrQuerySet
from geniza.footnotes.models import Creator, Footnote
@@ -1737,7 +1737,7 @@ class Meta:
@property
def standard_date_display(self):
"""Standard date in human-readable format for document details pages"""
- return Document.standard_date_display(self.standard_date)
+ return standard_date_display(self.standard_date)
class DocumentEventRelation(models.Model):
diff --git a/geniza/corpus/tests/test_dates.py b/geniza/corpus/tests/test_dates.py
index 12071bda3..418cc1a98 100644
--- a/geniza/corpus/tests/test_dates.py
+++ b/geniza/corpus/tests/test_dates.py
@@ -12,6 +12,7 @@
convert_seleucid_date,
get_hebrew_month,
get_islamic_month,
+ standard_date_display,
)
from geniza.corpus.models import Document
@@ -47,37 +48,6 @@ def test_original_date(self):
doc.doc_date_calendar = ""
assert doc.original_date == "507"
- def test_standard_date(self):
- # no dates; no error
- doc = Document()
- assert Document.standard_date_display(doc.doc_date_standard) is None
-
- # single day
- doc.doc_date_standard = "1569-10-23"
- assert (
- Document.standard_date_display(doc.doc_date_standard)
- == "23 October, 1569 CE"
- )
-
- # date range
- doc.doc_date_standard = "1839-03-17/1840-03-04"
- assert (
- Document.standard_date_display(doc.doc_date_standard)
- == "17 March, 1839 – 4 March, 1840 CE"
- )
-
- # year/month
- doc.doc_date_standard = "1839-03"
- assert Document.standard_date_display(doc.doc_date_standard) == "March 1839 CE"
-
- # year only
- doc.doc_date_standard = "1839"
- assert Document.standard_date_display(doc.doc_date_standard) == "1839 CE"
-
- # fallback behavior for dates in the wrong format
- doc.doc_date_standard = "1029–38"
- assert Document.standard_date_display(doc.doc_date_standard) == "1029–38 CE"
-
def test_document_date(self):
"""Should combine historical and converted dates"""
doc = Document(
@@ -350,3 +320,32 @@ def test_numeric_format(self):
assert PartialDate("1569").numeric_format("max") == "15691231"
# handle month-specific maxes
assert PartialDate("1569-02").numeric_format("max") == "15690228"
+
+
+def test_standard_date_display():
+ # no dates; no error
+ doc = Document()
+ assert standard_date_display(doc.doc_date_standard) is None
+
+ # single day
+ doc.doc_date_standard = "1569-10-23"
+ assert standard_date_display(doc.doc_date_standard) == "23 October, 1569 CE"
+
+ # date range
+ doc.doc_date_standard = "1839-03-17/1840-03-04"
+ assert (
+ standard_date_display(doc.doc_date_standard)
+ == "17 March, 1839 – 4 March, 1840 CE"
+ )
+
+ # year/month
+ doc.doc_date_standard = "1839-03"
+ assert standard_date_display(doc.doc_date_standard) == "March 1839 CE"
+
+ # year only
+ doc.doc_date_standard = "1839"
+ assert standard_date_display(doc.doc_date_standard) == "1839 CE"
+
+ # fallback behavior for dates in the wrong format
+ doc.doc_date_standard = "1029–38"
+ assert standard_date_display(doc.doc_date_standard) == "1029–38 CE"
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index 701da3e4f..3cb9d8f2e 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -12,7 +12,7 @@
from django.urls import path, reverse
from modeltranslation.admin import TabbedTranslationAdmin
-from geniza.corpus.dates import DocumentDateMixin
+from geniza.corpus.dates import standard_date_display
from geniza.corpus.models import DocumentEventRelation
from geniza.entities.forms import (
EventForm,
@@ -546,4 +546,4 @@ class EventAdmin(TabbedTranslationAdmin, SortableAdminBase, admin.ModelAdmin):
def automatic_date(self, obj):
"""Display automatically generated date/date range for an event as a formatted string"""
- return DocumentDateMixin.standard_date_display(obj.documents_date_range)
+ return standard_date_display(obj.documents_date_range)
diff --git a/geniza/entities/models.py b/geniza/entities/models.py
index 815fff470..5985a4d2f 100644
--- a/geniza/entities/models.py
+++ b/geniza/entities/models.py
@@ -12,7 +12,7 @@
from gfklookupwidget.fields import GfkLookupField
from geniza.common.models import cached_class_property
-from geniza.corpus.dates import DocumentDateMixin
+from geniza.corpus.dates import DocumentDateMixin, standard_date_display
from geniza.corpus.models import DisplayLabelMixin, Document, LanguageScript
from geniza.footnotes.models import Footnote
@@ -111,8 +111,8 @@ def date_str(self):
"""
return (
self.display_date
- or Document.standard_date_display(self.standard_date)
- or Document.standard_date_display(self.documents_date_range)
+ or standard_date_display(self.standard_date)
+ or standard_date_display(self.documents_date_range)
)
@property
diff --git a/geniza/entities/tests/test_entities_models.py b/geniza/entities/tests/test_entities_models.py
index 9a6ee1a1b..1abba4b0c 100644
--- a/geniza/entities/tests/test_entities_models.py
+++ b/geniza/entities/tests/test_entities_models.py
@@ -7,6 +7,7 @@
from django.forms import ValidationError
from django.utils import timezone
+from geniza.corpus.dates import standard_date_display
from geniza.corpus.models import Dating, Document
from geniza.entities.models import (
DocumentPlaceRelation,
@@ -453,13 +454,11 @@ def test_date_str(self, document):
document.events.add(event)
document.doc_date_standard = "1000/1010"
document.save()
- assert event.date_str == Document.standard_date_display(
- document.doc_date_standard
- )
+ assert event.date_str == standard_date_display(document.doc_date_standard)
# if defined, should use standard override date on event
event.standard_date = "1000/1099"
- assert event.date_str == Document.standard_date_display(event.standard_date)
+ assert event.date_str == standard_date_display(event.standard_date)
# if defined, should use display override date on event
event.display_date = "ca. 11th century"
From ff2e6859c62eebd49257446833306c6a1e0b36ee Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Fri, 29 Mar 2024 14:47:37 -0400
Subject: [PATCH 90/97] Move repeated date range logic to PartialDate static
method (#1512)
---
geniza/corpus/dates.py | 21 +++++++++++++++++++++
geniza/corpus/models.py | 21 +++++++--------------
geniza/entities/models.py | 22 ++++++++--------------
3 files changed, 36 insertions(+), 28 deletions(-)
diff --git a/geniza/corpus/dates.py b/geniza/corpus/dates.py
index 9774dea92..a55b8d4a3 100644
--- a/geniza/corpus/dates.py
+++ b/geniza/corpus/dates.py
@@ -126,6 +126,27 @@ def numeric_format(self, mode="min"):
See :meth:`isoformat` for more details."""
return self.isoformat(mode, "numeric")
+ @staticmethod
+ def get_date_range(old_range, new_range):
+ """Compute the min and max dates between two PartialDate ranges."""
+ minmax = old_range
+ [start, end] = new_range
+
+ # use numeric format to compare to current min, replace if smaller
+ start_numeric = int(start.numeric_format(mode="min"))
+ min = minmax[0]
+ if min is None or start_numeric < int(min.numeric_format(mode="min")):
+ # store as PartialDate, not numeric format
+ minmax[0] = start
+ # use numeric format to compare to current max, replace if larger
+ end_numeric = int(end.numeric_format(mode="max"))
+ max = minmax[1]
+ if max is None or end_numeric > int(max.numeric_format(mode="max")):
+ # store as PartialDate, not numeric format
+ minmax[1] = end
+
+ return minmax
+
class DocumentDateMixin(TrackChangesModel):
"""Mixin for document date fields (original and standardized),
diff --git a/geniza/corpus/models.py b/geniza/corpus/models.py
index 943f67ee1..d6b406499 100644
--- a/geniza/corpus/models.py
+++ b/geniza/corpus/models.py
@@ -962,6 +962,8 @@ def dating_range(self):
"""
# it is unlikely, but technically possible, that a document could have both on-document
# dates and inferred datings, so find the min and max out of all of them.
+
+ # start_date and end_date are PartialDate instances
dating_range = [self.start_date or None, self.end_date or None]
# bail out if we don't have any inferred datings
@@ -970,24 +972,15 @@ def dating_range(self):
# loop through inferred datings to find min and max among all dates (including both
# on-document and inferred)
- for dating in self.dating_set.all():
+ for inferred in self.dating_set.all():
# get start from standardized date range (formatted as "date1/date2" or "date")
- split_date = dating.standard_date.split("/")
+ split_date = inferred.standard_date.split("/")
start = PartialDate(split_date[0])
- # use numeric format to compare to current min, replace if smaller
- start_numeric = int(start.numeric_format(mode="min"))
- min = dating_range[0]
- if min is None or start_numeric < int(min.numeric_format(mode="min")):
- # store as PartialDate
- dating_range[0] = start
# get end from standardized date range
end = PartialDate(split_date[1]) if len(split_date) > 1 else start
- # use numeric format to compare to current max, replace if larger
- end_numeric = int(end.numeric_format(mode="max"))
- max = dating_range[1]
- if max is None or end_numeric > int(max.numeric_format(mode="max")):
- # store as PartialDate
- dating_range[1] = end
+ dating_range = PartialDate.get_date_range(
+ old_range=dating_range, new_range=[start, end]
+ )
return tuple(dating_range)
diff --git a/geniza/entities/models.py b/geniza/entities/models.py
index 5985a4d2f..92775cf28 100644
--- a/geniza/entities/models.py
+++ b/geniza/entities/models.py
@@ -12,7 +12,7 @@
from gfklookupwidget.fields import GfkLookupField
from geniza.common.models import cached_class_property
-from geniza.corpus.dates import DocumentDateMixin, standard_date_display
+from geniza.corpus.dates import DocumentDateMixin, PartialDate, standard_date_display
from geniza.corpus.models import DisplayLabelMixin, Document, LanguageScript
from geniza.footnotes.models import Footnote
@@ -119,24 +119,18 @@ def date_str(self):
def documents_date_range(self):
"""Standardized range of dates across associated documents"""
full_range = [None, None]
- # NOTE: This looks similar to Document.dating_range() but applies across multiple docs
for doc in self.documents.all():
+ # get each doc's full range, including inferred and on-document
doc_range = doc.dating_range()
+ # if doc has a range, and the range is not [None, None], compare
+ # it against the current min and max
if doc_range and doc_range[0]:
start = doc_range[0]
end = doc_range[1] if len(doc_range) > 1 else start
- # use numeric format to compare to current min, replace if smaller
- start_numeric = int(start.numeric_format(mode="min"))
- min = full_range[0]
- if min is None or start_numeric < int(min.numeric_format(mode="min")):
- # store as PartialDate
- full_range[0] = start
- # use numeric format to compare to current max, replace if larger
- end_numeric = int(end.numeric_format(mode="max"))
- max = full_range[1]
- if max is None or end_numeric > int(max.numeric_format(mode="max")):
- # store as PartialDate
- full_range[1] = end
+ # update full range with comparison results
+ full_range = PartialDate.get_date_range(
+ old_range=full_range, new_range=[start, end]
+ )
# prune Nones and use isoformat for standardized representation
full_range = [d.isoformat() for d in full_range if d]
From a9b9707c9cbe86727856a77ec08c20d7a8030e4d Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Tue, 2 Apr 2024 11:33:33 -0400
Subject: [PATCH 91/97] Update comment for get_date_range (#1512)
---
geniza/corpus/dates.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/geniza/corpus/dates.py b/geniza/corpus/dates.py
index a55b8d4a3..fafade4b6 100644
--- a/geniza/corpus/dates.py
+++ b/geniza/corpus/dates.py
@@ -128,7 +128,7 @@ def numeric_format(self, mode="min"):
@staticmethod
def get_date_range(old_range, new_range):
- """Compute the min and max dates between two PartialDate ranges."""
+ """Compute the union (widest possible date range) between two PartialDate ranges."""
minmax = old_range
[start, end] = new_range
From e16f853c34cfdaf8ff1b4147dff7aa0d8dccc059 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Wed, 3 Apr 2024 14:58:38 -0400
Subject: [PATCH 92/97] Highlight nostem matches on exact searches (#1570)
---
geniza/corpus/solr_queryset.py | 16 +++++-
.../corpus/tests/test_corpus_solrqueryset.py | 49 +++++++++++++++----
geniza/corpus/tests/test_corpus_views.py | 47 ++++++++++++++++--
geniza/corpus/views.py | 15 +++++-
solr_conf/conf/managed-schema | 8 +--
solr_conf/conf/solrconfig.xml | 6 ++-
6 files changed, 119 insertions(+), 22 deletions(-)
diff --git a/geniza/corpus/solr_queryset.py b/geniza/corpus/solr_queryset.py
index 8f4fef288..9983ea21c 100644
--- a/geniza/corpus/solr_queryset.py
+++ b/geniza/corpus/solr_queryset.py
@@ -76,6 +76,8 @@ class DocumentSolrQuerySet(AliasedSolrQuerySet):
"has_digital_translation": "has_digital_translation_b",
"has_discussion": "has_discussion_b",
"old_shelfmark": "old_shelfmark_bigram",
+ "transcription_nostem": "transcription_nostem",
+ "description_nostem": "description_nostem",
}
# regex to convert field aliases used in search to actual solr fields
@@ -131,7 +133,8 @@ def _search_term_cleanup(self, search_term):
self.highlight_query = search_term
# limit any exact phrase searches to non-stemmed field
exact_phrases = [
- f"content_nostem:{m}" for m in self.re_exact_match.findall(search_term)
+ f"(description_nostem:{m} OR transcription_nostem:{m})"
+ for m in self.re_exact_match.findall(search_term)
]
# add in judaeo-arabic conversion for the rest (double-quoted phrase should NOT be
# converted to JA, as this breaks if any brackets or other sigla are in doublequotes)
@@ -256,8 +259,17 @@ def get_highlighting(self):
"""highlight snippets within transcription/translation html may result in
invalid tags that will render strangely; clean up the html before returning"""
highlights = super().get_highlighting()
+ is_exact_search = "hl_query" in self.raw_params
for doc in highlights.keys():
- if "transcription" in highlights[doc]:
+ # _nostem fields should take precedence over stemmed fields in the case of an
+ # exact search; in that case, replace highlights for stemmed fields with nostem
+ if is_exact_search and "description_nostem" in highlights[doc]:
+ highlights[doc]["description"] = highlights[doc]["description_nostem"]
+ if is_exact_search and "transcription_nostem" in highlights[doc]:
+ highlights[doc]["transcription"] = [
+ clean_html(s) for s in highlights[doc]["transcription_nostem"]
+ ]
+ elif "transcription" in highlights[doc]:
highlights[doc]["transcription"] = [
clean_html(s) for s in highlights[doc]["transcription"]
]
diff --git a/geniza/corpus/tests/test_corpus_solrqueryset.py b/geniza/corpus/tests/test_corpus_solrqueryset.py
index 10715af16..fb7c7267c 100644
--- a/geniza/corpus/tests/test_corpus_solrqueryset.py
+++ b/geniza/corpus/tests/test_corpus_solrqueryset.py
@@ -127,7 +127,6 @@ def test_get_result_document_type(self, document):
):
# should match a DocumentType by name
result_doc = dqs.get_result_document(mock_doc)
- print(result_doc["type"])
assert isinstance(result_doc["type"], DocumentType)
# special case for Unknown type
@@ -167,34 +166,40 @@ def test_search_term_cleanup__arabic_to_ja(self):
# confirm arabic to judaeo-arabic runs here (with boost)
assert dqs._search_term_cleanup("دينار") == "(دينار^2.0|דינאר)"
# confirm arabic to judaeo-arabic does not run here
- assert dqs._search_term_cleanup('"دي[نا]ر"') == 'content_nostem:"دي[نا]ر"'
+ assert (
+ dqs._search_term_cleanup('"دي[نا]ر"')
+ == '(description_nostem:"دي[نا]ر" OR transcription_nostem:"دي[نا]ر")'
+ )
def test_search_term_cleanup__exact_match_regex(self):
dqs = DocumentSolrQuerySet()
- # double quotes scoped to fields should not become scoped to content_nostem field
- assert "content_nostem" not in dqs._search_term_cleanup('shelfmark:"T-S NS"')
- assert "content_nostem" not in dqs._search_term_cleanup(
+ # double quotes scoped to fields should not become scoped to nostem fields
+ assert "description_nostem" not in dqs._search_term_cleanup(
+ 'shelfmark:"T-S NS"'
+ )
+ assert "description_nostem" not in dqs._search_term_cleanup(
'tag:"marriage payment" shelfmark:"T-S NS"'
)
# double quotes for fuzzy/proximity searches should also not be scoped
- assert "content_nostem" not in dqs._search_term_cleanup('"divorced"~20')
- assert "content_nostem" not in dqs._search_term_cleanup('"he divorced"~20')
+ assert "description_nostem" not in dqs._search_term_cleanup('"divorced"~20')
+ assert "description_nostem" not in dqs._search_term_cleanup('"he divorced"~20')
# double quotes at the beginning of the query or after a space should be scoped (as well
# as repeated as an unscoped query)
assert (
- dqs._search_term_cleanup('"he divorced"') == 'content_nostem:"he divorced"'
+ dqs._search_term_cleanup('"he divorced"')
+ == '(description_nostem:"he divorced" OR transcription_nostem:"he divorced")'
)
- assert 'content_nostem:"he divorced"' in dqs._search_term_cleanup(
+ assert 'description_nostem:"he divorced"' in dqs._search_term_cleanup(
'shelfmark:"T-S NS" "he divorced"'
)
# should preserve order for e.g. boolean searches with exact matches
assert (
dqs._search_term_cleanup('"מרכב אלצלטאן" AND "אלמרכב אלצלטאן"')
- == 'content_nostem:"מרכב אלצלטאן" AND content_nostem:"אלמרכב אלצלטאן"'
+ == '(description_nostem:"מרכב אלצלטאן" OR transcription_nostem:"מרכב אלצלטאן") AND (description_nostem:"אלמרכב אלצלטאן" OR transcription_nostem:"אלמרכב אלצלטאן")'
)
def test_related_to(self, document, join, fragment, empty_solr):
@@ -268,3 +273,27 @@ def test_get_highlighting(self):
assert cleaned_highlight["doc.1"]["translation"] == [
clean_html("
bar baz")
]
+
+ def test_get_highlighting__exact_search(self):
+ dqs = DocumentSolrQuerySet()
+ with patch("geniza.corpus.solr_queryset.super") as mock_super:
+ mock_get_highlighting = mock_super.return_value.get_highlighting
+ test_highlight = {
+ "doc.1": {
+ "description": ["matched"],
+ "description_nostem": ["exactly matched"],
+ "transcription": ["match"],
+ "transcription_nostem": ["exact match"],
+ }
+ }
+ mock_get_highlighting.return_value = test_highlight
+ # no exact search was made; returned unchanged
+ assert dqs.get_highlighting() == test_highlight
+
+ # an exact search was made; now, the highlighting we actually use in the template
+ # ("description" and "transcription" keys) should be replaced w/ the nostem matches
+ dqs.raw_params["hl_query"] = "exact match"
+ assert dqs.get_highlighting()["doc.1"]["description"] == ["exactly matched"]
+ assert dqs.get_highlighting()["doc.1"]["transcription"][0] == clean_html(
+ "exact match"
+ )
diff --git a/geniza/corpus/tests/test_corpus_views.py b/geniza/corpus/tests/test_corpus_views.py
index 9af1a8908..ded5682fd 100644
--- a/geniza/corpus/tests/test_corpus_views.py
+++ b/geniza/corpus/tests/test_corpus_views.py
@@ -21,7 +21,7 @@
from geniza.common.utils import absolutize_url
from geniza.corpus.iiif_utils import EMPTY_CANVAS_ID, new_iiif_canvas
from geniza.corpus.models import Document, DocumentType, Fragment, TextBlock
-from geniza.corpus.solr_queryset import DocumentSolrQuerySet
+from geniza.corpus.solr_queryset import DocumentSolrQuerySet, clean_html
from geniza.corpus.views import (
DocumentAnnotationListView,
DocumentDetailView,
@@ -946,7 +946,7 @@ def test_last_modified(self, client, document):
assert new_last_modified != init_last_modified
def test_exact_match(self, empty_solr, document):
- # integration test for description exact match indexing (content_nostem)
+ # integration test for description exact match indexing (description_nostem)
doc1 = Document.objects.create(description_en="His son sells seashells")
doc2 = Document.objects.create(
description_en="Example of something a father sells to his son"
@@ -1004,7 +1004,7 @@ def test_ngram_highlighting(self, empty_solr):
@pytest.mark.django_db
def test_nostem_boost(self, empty_solr):
- # integration tests for boosting content_nostem to ensure exact matches in description are
+ # integration tests for boosting description_nostem to ensure exact matches in description are
# boosted above partial matches in shelfmark
harun_doc1 = Document.objects.create(
description_en="Story in Judaeo-Arabic, mentioning Ḥārūn b. Yaʿīsh and ʿAbd al-ʿAzīz al-Kohen."
@@ -1164,6 +1164,47 @@ def test_cleaned_transcription(self, source, empty_solr):
qs = docsearch_view.get_queryset()
assert qs.count() == 1
+ @pytest.mark.django_db
+ def test_exact_search_highlight(self, source, empty_solr):
+ # integration tests for exact search highlighting
+ document = Document.objects.create()
+ footnote = Footnote.objects.create(
+ content_object=document,
+ source=source,
+ doc_relation=Footnote.DIGITAL_EDITION,
+ )
+ Annotation.objects.create(
+ footnote=footnote,
+ content={
+ # body contains both a partial and an exact match for אלממ
+ "body": [{"value": "אלממחה ... אלממ"}],
+ "target": {
+ "source": {
+ "id": source.uri,
+ }
+ },
+ },
+ )
+ SolrClient().update.index([document.index_data()], commit=True)
+ docsearch_view = DocumentSearchView(kwargs={})
+ docsearch_view.request = Mock()
+
+ # no double quotes in search, should highlight entire phrase
+ docsearch_view.request.GET = {"q": "אלממ"}
+ dqs = docsearch_view.get_queryset()
+ assert dqs.get_highlighting()[f"document.{document.pk}"]["transcription"][
+ 0
+ ] == clean_html("אלממחה ... אלממ")
+
+ # double quotes in search, should highlight only the exact match
+ docsearch_view.request.GET = {"q": '"אלממ"'}
+ dqs = docsearch_view.get_queryset()
+ assert dqs.raw_params["hl_query"] == '"אלממ"'
+ assert (
+ clean_html("אלממ")
+ in dqs.get_highlighting()[f"document.{document.pk}"]["transcription"][0]
+ )
+
class TestDocumentScholarshipView:
def test_page_title(self, document, client, source):
diff --git a/geniza/corpus/views.py b/geniza/corpus/views.py
index f491cfc75..53aeb8cb4 100644
--- a/geniza/corpus/views.py
+++ b/geniza/corpus/views.py
@@ -171,7 +171,7 @@ def get_queryset(self):
if search_opts["q"]:
# NOTE: using requireFieldMatch so that field-specific search
- # terms will NOT be usind for highlighting text matches
+ # terms will NOT be used for highlighting text matches
# (unless they are in the appropriate field)
documents = (
documents.keyword_search(search_opts["q"])
@@ -181,6 +181,12 @@ def get_queryset(self):
method="unified",
requireFieldMatch=True,
)
+ .highlight(
+ "description_nostem",
+ snippets=3,
+ method="unified",
+ requireFieldMatch=True,
+ )
# return smaller chunk of highlighted text for transcriptions/translations
# since the lines are often shorter, resulting in longer text
.highlight(
@@ -197,6 +203,13 @@ def get_queryset(self):
fragsize=150,
requireFieldMatch=True,
)
+ .highlight(
+ "transcription_nostem",
+ method="unified",
+ fragsize=150,
+ requireFieldMatch=False,
+ **{"bs.type": "SEPARATOR", "bs.separator": "\n"},
+ )
# highlight old shelfmark so we can show match in results
.highlight("old_shelfmark", requireFieldMatch=True)
.also("score")
diff --git a/solr_conf/conf/managed-schema b/solr_conf/conf/managed-schema
index 67ba8bb2b..e6597a6a9 100644
--- a/solr_conf/conf/managed-schema
+++ b/solr_conf/conf/managed-schema
@@ -342,9 +342,9 @@
-
-
-
+
+
+
@@ -401,6 +401,6 @@
-
+
\ No newline at end of file
diff --git a/solr_conf/conf/solrconfig.xml b/solr_conf/conf/solrconfig.xml
index 3a18983e0..1e1834185 100644
--- a/solr_conf/conf/solrconfig.xml
+++ b/solr_conf/conf/solrconfig.xml
@@ -716,7 +716,8 @@
shelfmark_s^200
- content_nostem^190
+ description_nostem^190
+ transcription_nostem^190
shelfmark_bigram^180
shelfmark_textnum^140
description_en_bigram^50
@@ -741,7 +742,8 @@
shelfmark_bigram^9500
shelfmark_textnum^9500
description_en_bigram^3500
- content_nostem^3500
+ description_nostem^3500
+ transcription_nostem^3500
old_shelfmark_t
old_shelfmark_textnum
old_shelfmark_bigram^100
From 6ef2aa2786e5b02bcd79433a6494de195348334c Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Thu, 11 Apr 2024 15:14:02 -0400
Subject: [PATCH 93/97] Allow diacritic-insensitive searching in admin list
views for People/Places (#1479)
---
geniza/entities/admin.py | 27 +++++++++++++++++---
geniza/entities/tests/test_entities_admin.py | 14 ++++++++++
geniza/entities/tests/test_entities_views.py | 11 ++++++++
geniza/entities/views.py | 5 +++-
4 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index 3cb9d8f2e..759a116c4 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -4,6 +4,7 @@
from django.contrib import admin, messages
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
+from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models.fields import CharField, TextField
from django.forms import ModelChoiceField, ValidationError
from django.forms.models import ModelChoiceIterator
@@ -250,7 +251,7 @@ def get_formset(self, request, obj=None, **kwargs):
class PersonAdmin(TabbedTranslationAdmin, SortableAdminBase, admin.ModelAdmin):
"""Admin for Person entities in the PGP"""
- search_fields = ("names__name",)
+ search_fields = ("name_unaccented", "names__name")
fields = ("gender", "role", "has_page", "description")
inlines = (
NameInline,
@@ -285,7 +286,15 @@ def get_form(self, request, obj=None, **kwargs):
def get_queryset(self, request):
"""For autocomplete ONLY, remove self from queryset, so that Person-Person autocomplete
does not include self in the list of options"""
- qs = super().get_queryset(request)
+ # also add unaccented name to queryset so we can search on it
+ qs = (
+ super()
+ .get_queryset(request)
+ .annotate(
+ # ArrayAgg to group together related values from related model instances
+ name_unaccented=ArrayAgg("names__name__unaccent", distinct=True),
+ )
+ )
# only modify if this is the person-person autocomplete request
is_autocomplete = request and request.path == "/admin/autocomplete/"
@@ -448,7 +457,7 @@ def get_formset(self, request, obj=None, **kwargs):
class PlaceAdmin(SortableAdminBase, admin.ModelAdmin):
"""Admin for Place entities in the PGP"""
- search_fields = ("names__name",)
+ search_fields = ("name_unaccented", "names__name")
fields = (("latitude", "longitude"), "notes")
inlines = (
NameInline,
@@ -470,6 +479,18 @@ class PlaceAdmin(SortableAdminBase, admin.ModelAdmin):
"i", # FootnoteInline
)
+ def get_queryset(self, request):
+ """Modify queryset to add unaccented name annotation field, so that places
+ can be searched from admin list view without entering diacritics"""
+ return (
+ super()
+ .get_queryset(request)
+ .annotate(
+ # ArrayAgg to group together related values from related model instances
+ name_unaccented=ArrayAgg("names__name__unaccent", distinct=True),
+ )
+ )
+
@admin.register(PlacePlaceRelationType)
class PlacePlaceRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
diff --git a/geniza/entities/tests/test_entities_admin.py b/geniza/entities/tests/test_entities_admin.py
index 669ba35ff..486a18348 100644
--- a/geniza/entities/tests/test_entities_admin.py
+++ b/geniza/entities/tests/test_entities_admin.py
@@ -17,6 +17,7 @@
PersonPersonRelationTypeChoiceField,
PersonPersonReverseInline,
PersonPlaceInline,
+ PlaceAdmin,
)
from geniza.entities.models import (
Name,
@@ -290,3 +291,16 @@ def test_get_min_num(self, admin_client, document):
)
content = str(response.content)
assert 'name="documenteventrelation_set-MIN_NUM_FORMS" value="0"' in content
+
+
+@pytest.mark.django_db
+class TestPlaceAdmin:
+ def test_get_queryset(self):
+ # create a place
+ place = Place.objects.create()
+ Name.objects.create(name="Fusṭāṭ", content_object=place, primary=True)
+ place_admin = PlaceAdmin(Place, admin_site=admin.site)
+
+ # queryset should include name_unaccented field without diacritics
+ qs = place_admin.get_queryset(Mock())
+ assert qs.filter(name_unaccented__icontains="fustat").exists()
diff --git a/geniza/entities/tests/test_entities_views.py b/geniza/entities/tests/test_entities_views.py
index de6d42534..8a4d166b8 100644
--- a/geniza/entities/tests/test_entities_views.py
+++ b/geniza/entities/tests/test_entities_views.py
@@ -124,6 +124,12 @@ def test_get_queryset(self):
assert qs.count() == 1
assert qs.first().pk == person.pk
+ # should allow search by name WITH diacritics
+ person_autocomplete_view.request.GET = {"q": "Ḥayyim"}
+ qs = person_autocomplete_view.get_queryset()
+ assert qs.count() == 1
+ assert qs.first().pk == person_2.pk
+
class TestPlaceAutocompleteView:
@pytest.mark.django_db
@@ -135,6 +141,11 @@ def test_get_queryset(self):
# should filter on place name, case and diacritic insensitive
place_autocomplete_view.request = Mock()
+ place_autocomplete_view.request.GET = {"q": "Fusṭāṭ"}
+ qs = place_autocomplete_view.get_queryset()
+ assert qs.count() == 1
+ assert qs.first().pk == place.pk
+
place_autocomplete_view.request.GET = {"q": "fustat"}
qs = place_autocomplete_view.get_queryset()
assert qs.count() == 1
diff --git a/geniza/entities/views.py b/geniza/entities/views.py
index bdc86b340..bca9f59e3 100644
--- a/geniza/entities/views.py
+++ b/geniza/entities/views.py
@@ -2,6 +2,7 @@
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.postgres.aggregates import ArrayAgg
+from django.db.models import Q
from django.forms import ValidationError
from django.http import HttpResponseRedirect
from django.urls import reverse
@@ -85,7 +86,9 @@ def get_queryset(self):
name_unaccented=ArrayAgg("names__name__unaccent", distinct=True),
).order_by("name_unaccented")
if q:
- qs = qs.filter(name_unaccented__icontains=q)
+ qs = qs.filter(
+ Q(name_unaccented__icontains=q) | Q(names__name__icontains=q)
+ ).distinct()
return qs
From 84d5e29bb6c1d0cfee4c707c0edb2424c1011b26 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Wed, 24 Apr 2024 13:25:22 -0400
Subject: [PATCH 94/97] Set version to 4.17 and document changes
---
CHANGELOG.rst | 31 +++++++++++++++++++++++++++++++
geniza/__init__.py | 2 +-
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 347b4ed9d..9e7283683 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,37 @@
Change Log
==========
+4.17
+----
+
+- public site
+ - As a public site user, I would like to see date ranges separated with an en-dash (–) instead of an em-dash (—).
+ - As a front end user, I only want to see one document number for a source displayed in the scholarship records on the public site.
+ - As a frontend user, I want to see dating information displayed on document details when available, so that I can find out the time frame of a document when it is known.
+ - bugfix: Double quotes search returning unexpected results
+ - bugfix: Issues with shelfmark scoped search
+ - bugfix: Highlighting context shows entire transcription or translation in search result
+ - bugfix: Transcription search results not always formatted correctly
+ - bugfix: Bracket and other character search is functioning unpredictably
+ - bugfix: Incorrect words are highlighted in complete word quotation search (Hebrew script)
+ - bugfix: Some partial search results in description not boosted by relevancy
+ - chore: accessibility issues flagged by DubBot
+
+- image, transcription, translation viewer/editor
+ - As a transcription editor, I should see an error if I try to update an annotation with out of date content so that I don't overwrite someone else's changes.
+ - bugfix: Autofill for source search (when inputting a transcription source) not functioning properly
+
+- admin
+ - As a content editor, I want to record places-to-places relationship on the place page and on the document detail page, so that I can track ambiguity.
+ - As a content admin, I want to drop down a pin on a map and then be able to move the pin around so that I can manually adjust the coordinates of a place before saving the location.
+ - As a content editor, I want there to be a notes field in the places pages so that I can add more detail about places that are hard-to-find.
+ - As a content admin, I want a provenance field on the document detail page so that I can note the origin and aquisition history of fragments when available.
+ - As a content editor, I want clearer help text for the name field of the person page so I know how best to present people's names on their pages
+ - As a content editor, I would like to see Historic Shelfmark on the Document edit page, to ensure that my work is correct when working with old scholarship.
+ - bugfix: Full shelfmark search for multiple shelfmarks not working in admin
+ - bugfix: Invalid lat/long coordinates are allowed for Places, but don't persist
+ - bugfix: People names are not diacritic neutral when adding them from Document Detail page
+
4.16.1
------
diff --git a/geniza/__init__.py b/geniza/__init__.py
index 23fdeebb2..cf15af50b 100644
--- a/geniza/__init__.py
+++ b/geniza/__init__.py
@@ -1,4 +1,4 @@
-__version_info__ = (4, 17, 0, "dev")
+__version_info__ = (4, 17, 0, None)
# Dot-connect all but the last. Last is dash-connected if not None.
From 4f900361dd8821ad15fb02d7a76ab65d21ab8763 Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Wed, 24 Apr 2024 14:34:01 -0400
Subject: [PATCH 95/97] Bump internal JS dependency annotorious-tahqiq
---
package-lock.json | 15 +++++++--------
package.json | 2 +-
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 889cfe2a1..c735c8f69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,7 @@
"@recogito/annotorious": "^2.7.13",
"@recogito/annotorious-openseadragon": "^2.7.17",
"angle-input": "^0.0.1",
- "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
+ "annotorious-tahqiq": "^1.3.0",
"autoprefixer": "^10.3.2",
"babel-loader": "^8.2.2",
"core-js": "^3.16.3",
@@ -2556,10 +2556,9 @@
"integrity": "sha512-jq2/ZAjbS3yoICQuAqMEVty2tJdrS6GW4wRExmqHScqno41II0C/wZ0e8fQ/hJ2xUB7CSpfEcbjC/tec57o/Hg=="
},
"node_modules/annotorious-tahqiq": {
- "version": "1.2.0",
- "resolved": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#8a6f8ea5259c08215057f82df77310012a4dfb9b",
- "integrity": "sha512-ThptpzC8GtkhW3g66D+21pQoDTZVzo5Z/Manyu+tNggvdmInXB8Pow/i6QOhh+v0152Vtm4UHFpDE/x+f5oetA==",
- "license": "Apache-2.0",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/annotorious-tahqiq/-/annotorious-tahqiq-1.3.0.tgz",
+ "integrity": "sha512-KjpptMfpW0djhe7oUXvIKanf3B26kZMdr24VHjIFa7Fb/kho5u8kQ9iKaEwQ2F1yL2DjYPLTiHguTw57FZM4pg==",
"dependencies": {
"@tinymce/tinymce-webcomponent": "^2.0.0",
"@ungap/custom-elements": "^1.1.0"
@@ -10912,9 +10911,9 @@
"integrity": "sha512-jq2/ZAjbS3yoICQuAqMEVty2tJdrS6GW4wRExmqHScqno41II0C/wZ0e8fQ/hJ2xUB7CSpfEcbjC/tec57o/Hg=="
},
"annotorious-tahqiq": {
- "version": "git+ssh://git@github.com/Princeton-CDH/annotorious-tahqiq.git#8a6f8ea5259c08215057f82df77310012a4dfb9b",
- "integrity": "sha512-ThptpzC8GtkhW3g66D+21pQoDTZVzo5Z/Manyu+tNggvdmInXB8Pow/i6QOhh+v0152Vtm4UHFpDE/x+f5oetA==",
- "from": "annotorious-tahqiq@github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/annotorious-tahqiq/-/annotorious-tahqiq-1.3.0.tgz",
+ "integrity": "sha512-KjpptMfpW0djhe7oUXvIKanf3B26kZMdr24VHjIFa7Fb/kho5u8kQ9iKaEwQ2F1yL2DjYPLTiHguTw57FZM4pg==",
"requires": {
"@tinymce/tinymce-webcomponent": "^2.0.0",
"@ungap/custom-elements": "^1.1.0"
diff --git a/package.json b/package.json
index 25f08fcfe..42d3d640a 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"@recogito/annotorious": "^2.7.13",
"@recogito/annotorious-openseadragon": "^2.7.17",
"angle-input": "^0.0.1",
- "annotorious-tahqiq": "github:Princeton-CDH/annotorious-tahqiq#8a6f8ea5259c08215057f82df77310012a4dfb9b",
+ "annotorious-tahqiq": "^1.3.0",
"autoprefixer": "^10.3.2",
"babel-loader": "^8.2.2",
"core-js": "^3.16.3",
From 6525d4e00ee5eac921405f25a278e21fef7679cf Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Wed, 24 Apr 2024 14:34:25 -0400
Subject: [PATCH 96/97] Use npm audit fix to fix vulnerabilities
---
package-lock.json | 151 ++++++++++++++++++++++++----------------------
1 file changed, 79 insertions(+), 72 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index c735c8f69..5bd0eab47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2875,13 +2875,13 @@
"dev": true
},
"node_modules/body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -2889,7 +2889,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -3292,9 +3292,9 @@
]
},
"node_modules/content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -3309,9 +3309,9 @@
}
},
"node_modules/cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -4078,17 +4078,17 @@
}
},
"node_modules/express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -4362,9 +4362,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.4",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
- "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -4436,9 +4436,9 @@
"dev": true
},
"node_modules/fs-monkey": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
- "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
+ "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
"dev": true
},
"node_modules/fs.realpath": {
@@ -5027,9 +5027,9 @@
}
},
"node_modules/ip": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
+ "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
"dev": true
},
"node_modules/ipaddr.js": {
@@ -5685,12 +5685,12 @@
}
},
"node_modules/memfs": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.0.tgz",
- "integrity": "sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
"dev": true,
"dependencies": {
- "fs-monkey": "1.0.3"
+ "fs-monkey": "^1.0.4"
},
"engines": {
"node": ">= 4.0.0"
@@ -6872,9 +6872,9 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
@@ -8513,19 +8513,26 @@
}
},
"node_modules/webpack-dev-middleware": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz",
- "integrity": "sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg==",
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
+ "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",
- "memfs": "^3.2.2",
+ "memfs": "^3.4.3",
"mime-types": "^2.1.31",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
},
"engines": {
"node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/webpack-dev-middleware/node_modules/ajv": {
@@ -11164,13 +11171,13 @@
"dev": true
},
"body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -11178,7 +11185,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -11474,9 +11481,9 @@
}
},
"content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true
},
"convert-source-map": {
@@ -11488,9 +11495,9 @@
}
},
"cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true
},
"cookie-signature": {
@@ -12109,17 +12116,17 @@
}
},
"express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -12330,9 +12337,9 @@
}
},
"follow-redirects": {
- "version": "1.15.4",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
- "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
},
"for-each": {
@@ -12378,9 +12385,9 @@
"dev": true
},
"fs-monkey": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
- "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
+ "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
"dev": true
},
"fs.realpath": {
@@ -12819,9 +12826,9 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw=="
},
"ip": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
+ "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==",
"dev": true
},
"ipaddr.js": {
@@ -13311,12 +13318,12 @@
"dev": true
},
"memfs": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.0.tgz",
- "integrity": "sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
+ "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
"dev": true,
"requires": {
- "fs-monkey": "1.0.3"
+ "fs-monkey": "^1.0.4"
}
},
"memoize-one": {
@@ -14204,9 +14211,9 @@
"dev": true
},
"raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
@@ -15474,13 +15481,13 @@
}
},
"webpack-dev-middleware": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz",
- "integrity": "sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg==",
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
+ "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"requires": {
"colorette": "^2.0.10",
- "memfs": "^3.2.2",
+ "memfs": "^3.4.3",
"mime-types": "^2.1.31",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
From db55161c4df0fb3b14c9e17d4551d907fee30fae Mon Sep 17 00:00:00 2001
From: Ben Silverman
Date: Wed, 24 Apr 2024 14:40:15 -0400
Subject: [PATCH 97/97] Dcument working pip package versions
---
requirements.lock | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/requirements.lock b/requirements.lock
index cf99bae56..312203bab 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -18,7 +18,7 @@ commonmark==0.9.1
convertdate==2.4.0
coverage==6.5.0
distlib==0.3.6
-Django==3.2.16
+Django==3.2.23
django-admin-inline-paginator==0.3.0
django-admin-sortable2==1.0.4
django-adminlogentries==0.1.2
@@ -35,6 +35,7 @@ django-gfklookupwidget==1.0.9
django-modelcluster==5.3
django-modeltranslation==0.17.5
django-multiselectfield==0.1.12
+django-select2==7.10.0
django-split-settings==1.2.0
django-tabular-export==1.1.0
django-taggit==1.5.1
@@ -120,11 +121,13 @@ sqlparse==0.4.3
tablib==3.2.1
taggit-selectize==2.11.0
telepath==0.3
+text-unidecode==1.3
toml==0.10.2
tomli==2.0.1
trio==0.22.0
trio-websocket==0.9.2
typing_extensions==4.4.0
+unicode==2.9
Unidecode==1.3.6
urllib3==1.26.12
virtualenv==20.16.7