"""ISNI author checking utilities"""
import xml.etree.ElementTree as ET
import requests

from bookwyrm import activitypub, models


def request_isni_data(search_index, search_term, max_records=5):
    """Request data from the ISNI API"""

    search_string = f'{search_index}="{search_term}"'
    query_params = {
        "query": search_string,
        "version": "1.1",
        "operation": "searchRetrieve",
        "recordSchema": "isni-b",
        "maximumRecords": max_records,
        "startRecord": "1",
        "recordPacking": "xml",
        "sortKeys": "RLV,pica,0,,",
    }
    result = requests.get("http://isni.oclc.org/sru/", params=query_params, timeout=10)
    # the OCLC ISNI server asserts the payload is encoded
    # in latin1, but we know better
    result.encoding = "utf-8"
    return result.text


def make_name_string(element):
    """create a string of form 'personal_name surname'"""

    # NOTE: this will often be incorrect, many naming systems
    # list "surname" before personal name
    forename = element.find(".//forename")
    surname = element.find(".//surname")
    if forename is not None:
        return "".join([forename.text, " ", surname.text])
    return surname.text


def get_other_identifier(element, code):
    """Get other identifiers associated with an author from their ISNI record"""

    identifiers = element.findall(".//otherIdentifierOfIdentity")
    for section_head in identifiers:
        if (
            section_head.find(".//type") is not None
            and section_head.find(".//type").text == code
            and section_head.find(".//identifier") is not None
        ):
            return section_head.find(".//identifier").text

    # if we can't find it in otherIdentifierOfIdentity,
    # try sources
    for source in element.findall(".//sources"):
        code_of_source = source.find(".//codeOfSource")
        if code_of_source is not None and code_of_source.text.lower() == code.lower():
            return source.find(".//sourceIdentifier").text

    return ""


def get_external_information_uri(element, match_string):
    """Get URLs associated with an author from their ISNI record"""

    sources = element.findall(".//externalInformation")
    for source in sources:
        information = source.find(".//information")
        uri = source.find(".//URI")
        if (
            uri is not None
            and information is not None
            and information.text.lower() == match_string.lower()
        ):
            return uri.text
    return ""


def find_authors_by_name(name_string, description=False):
    """Query the ISNI database for possible author matches by name"""

    payload = request_isni_data("pica.na", name_string)
    # parse xml
    root = ET.fromstring(payload)
    # build list of possible authors
    possible_authors = []
    for element in root.iter("responseRecord"):
        personal_name = element.find(".//forename/..")
        if not personal_name:
            continue

        author = get_author_from_isni(element.find(".//isniUnformatted").text)

        if bool(description):

            titles = []
            # prefer title records from LoC+ coop, Australia, Ireland, or Singapore
            # in that order
            for source in ["LCNACO", "NLA", "N6I", "NLB"]:
                for parent in element.findall(f'.//titleOfWork/[@source="{source}"]'):
                    titles.append(parent.find(".//title"))
                for parent in element.findall(f'.//titleOfWork[@subsource="{source}"]'):
                    titles.append(parent.find(".//title"))
            # otherwise just grab the first title listing
            titles.append(element.find(".//title"))

            if titles is not None:
                # some of the "titles" in ISNI are a little ...iffy
                # '@' is used by ISNI/OCLC to index the starting point ignoring stop words
                # (e.g. "The @Government of no one")
                title_elements = [
                    e for e in titles if not e.text.replace("@", "").isnumeric()
                ]
                if len(title_elements):
                    author.bio = title_elements[0].text.replace("@", "")
                else:
                    author.bio = None

        possible_authors.append(author)

    return possible_authors


def get_author_from_isni(isni):
    """Find data to populate a new author record from their ISNI"""

    payload = request_isni_data("pica.isn", isni)
    # parse xml
    root = ET.fromstring(payload)
    # there should only be a single responseRecord
    # but let's use the first one just in case
    element = root.find(".//responseRecord")
    name = make_name_string(element.find(".//forename/.."))
    viaf = get_other_identifier(element, "viaf")
    # use a set to dedupe aliases in ISNI
    aliases = set()
    aliases_element = element.findall(".//personalNameVariant")
    for entry in aliases_element:
        aliases.add(make_name_string(entry))
    # aliases needs to be list not set
    aliases = list(aliases)
    bio = element.find(".//nameTitle")
    bio = bio.text if bio is not None else ""
    wikipedia = get_external_information_uri(element, "Wikipedia")

    author = activitypub.Author(
        id=element.find(".//isniURI").text,
        name=name,
        isni=isni,
        viafId=viaf,
        aliases=aliases,
        bio=bio,
        wikipediaLink=wikipedia,
    )

    return author


def build_author_from_isni(match_value):
    """Build basic author class object from ISNI URL"""

    # if it is an isni value get the data
    if match_value.startswith("https://isni.org/isni/"):
        isni = match_value.replace("https://isni.org/isni/", "")
        return {"author": get_author_from_isni(isni)}
    # otherwise it's a name string
    return {}


def augment_author_metadata(author, isni):
    """Update any missing author fields from ISNI data"""

    isni_author = get_author_from_isni(isni)
    isni_author.to_model(model=models.Author, instance=author, overwrite=False)

    # we DO want to overwrite aliases because we're adding them to the
    # existing aliases and ISNI will usually have more.
    # We need to dedupe because ISNI records often have lots of dupe aliases
    aliases = set(isni_author.aliases)
    for alias in author.aliases:
        aliases.add(alias)
    author.aliases = list(aliases)
    author.save()

bookwyrm/utils/isni.py
"""ISNI author checking utilities"""
import xml.etree.ElementTree as ET
import requests

from bookwyrm import activitypub, models


def request_isni_data(search_index, search_term, max_records=5):
    """Request data from the ISNI API"""

    search_string = f'{search_index}="{search_term}"'
    query_params = {
        "query": search_string,
        "version": "1.1",
        "operation": "searchRetrieve",
        "recordSchema": "isni-b",
        "maximumRecords": max_records,
        "startRecord": "1",
        "recordPacking": "xml",
        "sortKeys": "RLV,pica,0,,",
    }
    result = requests.get("http://isni.oclc.org/sru/", params=query_params, timeout=15)
    # the OCLC ISNI server asserts the payload is encoded
    # in latin1, but we know better
    result.encoding = "utf-8"
    return result.text


def make_name_string(element):
    """create a string of form 'personal_name surname'"""

    # NOTE: this will often be incorrect, many naming systems
    # list "surname" before personal name
    forename = element.find(".//forename")
    surname = element.find(".//surname")
    if forename is not None:
        return "".join([forename.text, " ", surname.text])
    return surname.text


def get_other_identifier(element, code):
    """Get other identifiers associated with an author from their ISNI record"""

    identifiers = element.findall(".//otherIdentifierOfIdentity")
    for section_head in identifiers:
        if (
            section_head.find(".//type") is not None
            and section_head.find(".//type").text == code
            and section_head.find(".//identifier") is not None
        ):
            return section_head.find(".//identifier").text

    # if we can't find it in otherIdentifierOfIdentity,
    # try sources
    for source in element.findall(".//sources"):
        code_of_source = source.find(".//codeOfSource")
        if code_of_source is not None and code_of_source.text.lower() == code.lower():
            return source.find(".//sourceIdentifier").text

    return ""


def get_external_information_uri(element, match_string):
    """Get URLs associated with an author from their ISNI record"""

    sources = element.findall(".//externalInformation")
    for source in sources:
        information = source.find(".//information")
        uri = source.find(".//URI")
        if (
            uri is not None
            and information is not None
            and information.text.lower() == match_string.lower()
        ):
            return uri.text
    return ""


def find_authors_by_name(name_string, description=False):
    """Query the ISNI database for possible author matches by name"""

    payload = request_isni_data("pica.na", name_string)
    # parse xml
    root = ET.fromstring(payload)
    # build list of possible authors
    possible_authors = []
    for element in root.iter("responseRecord"):
        personal_name = element.find(".//forename/..")
        if not personal_name:
            continue

        author = get_author_from_isni(element.find(".//isniUnformatted").text)

        if bool(description):

            titles = []
            # prefer title records from LoC+ coop, Australia, Ireland, or Singapore
            # in that order
            for source in ["LCNACO", "NLA", "N6I", "NLB"]:
                for parent in element.findall(f'.//titleOfWork/[@source="{source}"]'):
                    titles.append(parent.find(".//title"))
                for parent in element.findall(f'.//titleOfWork[@subsource="{source}"]'):
                    titles.append(parent.find(".//title"))
            # otherwise just grab the first title listing
            titles.append(element.find(".//title"))

            if titles is not None:
                # some of the "titles" in ISNI are a little ...iffy
                # '@' is used by ISNI/OCLC to index the starting point ignoring stop words
                # (e.g. "The @Government of no one")
                title_elements = [
                    e for e in titles if not e.text.replace("@", "").isnumeric()
                ]
                if len(title_elements):
                    author.bio = title_elements[0].text.replace("@", "")
                else:
                    author.bio = None

        possible_authors.append(author)

    return possible_authors


def get_author_from_isni(isni):
    """Find data to populate a new author record from their ISNI"""

    payload = request_isni_data("pica.isn", isni)
    # parse xml
    root = ET.fromstring(payload)
    # there should only be a single responseRecord
    # but let's use the first one just in case
    element = root.find(".//responseRecord")
    name = make_name_string(element.find(".//forename/.."))
    viaf = get_other_identifier(element, "viaf")
    # use a set to dedupe aliases in ISNI
    aliases = set()
    aliases_element = element.findall(".//personalNameVariant")
    for entry in aliases_element:
        aliases.add(make_name_string(entry))
    # aliases needs to be list not set
    aliases = list(aliases)
    bio = element.find(".//nameTitle")
    bio = bio.text if bio is not None else ""
    wikipedia = get_external_information_uri(element, "Wikipedia")

    author = activitypub.Author(
        id=element.find(".//isniURI").text,
        name=name,
        isni=isni,
        viafId=viaf,
        aliases=aliases,
        bio=bio,
        wikipediaLink=wikipedia,
    )

    return author


def build_author_from_isni(match_value):
    """Build basic author class object from ISNI URL"""

    # if it is an isni value get the data
    if match_value.startswith("https://isni.org/isni/"):
        isni = match_value.replace("https://isni.org/isni/", "")
        return {"author": get_author_from_isni(isni)}
    # otherwise it's a name string
    return {}


def augment_author_metadata(author, isni):
    """Update any missing author fields from ISNI data"""

    isni_author = get_author_from_isni(isni)
    isni_author.to_model(model=models.Author, instance=author, overwrite=False)

    # we DO want to overwrite aliases because we're adding them to the
    # existing aliases and ISNI will usually have more.
    # We need to dedupe because ISNI records often have lots of dupe aliases
    aliases = set(isni_author.aliases)
    for alias in author.aliases:
        aliases.add(alias)
    author.aliases = list(aliases)
    author.save()

bookwyrm/utils/isni.py
diff --git a/bookwyrm/templates/directory/directory.html b/bookwyrm/templates/directory/directory.html
index 9753c4c0f8..c3ddb3c5c4 100644
--- a/bookwyrm/templates/directory/directory.html
+++ b/bookwyrm/templates/directory/directory.html
@@ -18,7 +18,7 @@ <h1 class="title">
     </p>
     <form name="directory" method="POST" action="{% url 'directory' %}">
         {% csrf_token %}
-        <button class="button is-primary" type="submit">Join Directory</button>
+        <button class="button is-primary" type="submit">{% trans "Join Directory" %}</button>
         <p class="help">
             {% url 'prefs-profile' as path %}
             {% blocktrans with path=path %}You can opt-out at any time in your <a href="{{ path }}">profile settings.</a>{% endblocktrans %}
@@ -28,7 +28,7 @@ <h1 class="title">
     <div class="column is-narrow">
         {% trans "Dismiss message" as button_text %}
         <button type="button" class="delete set-display" data-id="hide_join_directory" data-value="true">
-            <span>Dismiss message</span>
+            <span>{% trans "Dismiss message" %}</span>
         </button>
     </div>
 </div></div>
diff --git a/bookwyrm/templates/get_started/book_preview.html b/bookwyrm/templates/get_started/book_preview.html
index 893e7593a2..8a20d0d773 100644
--- a/bookwyrm/templates/get_started/book_preview.html
+++ b/bookwyrm/templates/get_started/book_preview.html
@@ -4,9 +4,14 @@
 
     <div class="select is-small mt-1 mb-3">
         <select name="{{ book.id }}" aria-label="{% blocktrans with book_title=book.title %}Have you read {{ book_title }}?{% endblocktrans %}">
-            <option disabled selected value>Add to your books</option>
+            <option disabled selected value>{% trans 'Add to your books' %}</option>
             {% for shelf in user_shelves %}
-            <option value="{{ shelf.id }}">{{ shelf.name }}</option>
+            <option value="{{ shelf.id }}">
+                {% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
+                {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
+                {% elif shelf.identifier == 'read' %}{% trans "Read" %}
+                {% else %}{{ shelf.name }}{% endif %}
+            </option>
             {% endfor %}
         </select>
     </div>
diff --git a/bookwyrm/templates/search/book.html b/bookwyrm/templates/search/book.html
index 704f055bf6..66adb8c837 100644
--- a/bookwyrm/templates/search/book.html
+++ b/bookwyrm/templates/search/book.html
@@ -39,7 +39,7 @@
         <header class="columns is-mobile">
             <div class="column">
                 <h3 class="title is-5">
-                    Results from
+                    {% trans 'Results from' %}
                     <a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a>
                 </h3>
             </div>
diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html
index 01d41aa079..0184ab1d88 100644
--- a/bookwyrm/templates/shelf/shelf.html
+++ b/bookwyrm/templates/shelf/shelf.html
@@ -80,7 +80,10 @@ <h1 class="title">
     <div class="block columns is-mobile">
         <div class="column">
             <h2 class="title is-3">
-                {{ shelf.name }}
+                {% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
+                {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
+                {% elif shelf.identifier == 'read' %}{% trans "Read" %}
+                {% else %}{{ shelf.name }}{% endif %}
                 <span class="subtitle">
                     {% include 'snippets/privacy-icons.html' with item=shelf %}
                 </span>
diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html
index 4b32f5a8e2..3ee6fa92c6 100644
--- a/bookwyrm/templates/snippets/shelf_selector.html
+++ b/bookwyrm/templates/snippets/shelf_selector.html
@@ -22,7 +22,15 @@
         <input type="hidden" name="book" value="{{ book.id }}">
         <input type="hidden" name="change-shelf-from" value="{{ current.identifier }}">
         <input type="hidden" name="shelf" value="{{ shelf.identifier }}">
-        <button class="button is-fullwidth is-small shelf-option is-radiusless is-white" type="submit" {% if shelf in book.shelf_set.all %} disabled {% endif %}><span>{{ shelf.name }}</span></button>
+
+        <button class="button is-fullwidth is-small shelf-option is-radiusless is-white" type="submit" {% if shelf.identifier == current.identifier %}disabled{% endif %}>
+            <span>
+                {% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
+                {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
+                {% elif shelf.identifier == 'read' %}{% trans "Read" %}
+                {% else %}{{ shelf.name }}{% endif %}
+            </span>
+        </button>
     </form>
 </li>
 {% else%}
diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html
index 36e646aa9e..ccc4a44ea8 100755
--- a/bookwyrm/templates/user/user.html
+++ b/bookwyrm/templates/user/user.html
@@ -29,8 +29,13 @@ <h2 class="title">
     <div class="columns is-mobile scroll-x">
         {% for shelf in shelves %}
         <div class="column is-narrow">
-            <h3>{{ shelf.name }}
-                {% if shelf.size > 3 %}<small>(<a href="{{ shelf.local_path }}">{% blocktrans with size=shelf.size %}View all {{ size }}{% endblocktrans %}</a>)</small>{% endif %}</h3>
+            <h3>
+                {% if shelf.name == 'To Read' %}{% trans "To Read" %}
+                {% elif shelf.name == 'Currently Reading' %}{% trans "Currently Reading" %}
+                {% elif shelf.name == 'Read' %}{% trans "Read" %}
+                {% else %}{{ shelf.name }}{% endif %}
+                {% if shelf.size > 3 %}<small>(<a href="{{ shelf.local_path }}">{% blocktrans with size=shelf.size %}View all {{ size }}{% endblocktrans %}</a>)</small>{% endif %}
+            </h3>
             <div class="is-mobile field is-grouped">
             {% for book in shelf.books %}
             <div class="control">
@@ -49,7 +54,8 @@ <h3>{{ shelf.name }}
 
 {% if goal %}
 <div class="block">
-    <h2 class="title">{% now 'Y' %} Reading Goal</h2>
+    {% now 'Y' as current_year%}
+    <h2 class="title">{% blocktrans %}{{ current_year }} Reading Goal{% endblocktrans %}</h2>
     {% include 'snippets/goal_progress.html' with goal=goal %}
 </div>
 {% endif %}
diff --git a/bookwyrm/utils/isni.py b/bookwyrm/utils/isni.py
index a35c3f2493..65d20c85cc 100644
--- a/bookwyrm/utils/isni.py
+++ b/bookwyrm/utils/isni.py
@@ -19,7 +19,7 @@ def request_isni_data(search_index, search_term, max_records=5):
         "recordPacking": "xml",
         "sortKeys": "RLV,pica,0,,",
     }
-    result = requests.get("http://isni.oclc.org/sru/", params=query_params, timeout=10)
+    result = requests.get("http://isni.oclc.org/sru/", params=query_params, timeout=15)
     # the OCLC ISNI server asserts the payload is encoded
     # in latin1, but we know better
     result.encoding = "utf-8"

Some translation strings missing/not applied
**Describe the bug**
Some translations are not being applied.

**To Reproduce**

change Bookwyrm language to other than English (tested with Lithuanian)

https://ziurkes.group.lt/user/athinkingmeat/books/read
https://ziurkes.group.lt/user/athinkingmeat/books/reading
https://ziurkes.group.lt/user/athinkingmeat/books/to-read

**Expected behavior**
All these links should have "read", "currently reading" and "to read" strings translated, but they are shown in English

**Screenshots**

![image](https://user-images.githubusercontent.com/81133/143778383-c5e6f8b2-4925-4060-a886-e40b9b6361fb.png)

![image](https://user-images.githubusercontent.com/81133/143778390-5ba3cc06-6744-4a60-a855-27fb3202f690.png)

![image](https://user-images.githubusercontent.com/81133/143778375-e799c900-1737-4025-8d1e-80ea99b3df81.png)


**Instance**

https://ziurkes.group.lt/

**Additional context**
Probably is a problem with other languages as well

