Skip to content

Instantly share code, notes, and snippets.

@gregoriopellegrino
Last active August 14, 2019 00:03
Show Gist options
  • Save gregoriopellegrino/c94c46933d660a3db233e7a1c9c6f5e6 to your computer and use it in GitHub Desktop.
Save gregoriopellegrino/c94c46933d660a3db233e7a1c9c6f5e6 to your computer and use it in GitHub Desktop.
Very simple script in jQuery to find all the languages present in an HTML document. Useful for controlling the accessibility of documents. Improvements are welcome. #a11y Made to run from the browser development console
// main lang
if ($("html").get(0).hasAttribute("lang")) {
var main_lang = $("html").attr("lang");
} else {
var main_lang = $("body").attr("lang");
}
console.log("Main language is "+main_lang);
// children lang
var langs = [];
$("*[lang]").each(function() {
var lang = $(this).attr("lang");
if ($.inArray(lang, langs) == -1) {
langs.push(lang)
}
});
console.log("Other languages found: "+langs);
@gregoriopellegrino
Copy link
Author

Thanks!

@JayPanoz
Copy link

No problem.

That said, trying to handle errors kinda opens a can of worms:

  • I’m trimming the value (leading and trailing spaces) for empty value, but that should probably be flagged for non-empty value as well, cf. HTML standard;
  • Also, in the XHTML “cheat code”, I’m adding a lang attribute if there is none (as pure commodity so that the other code can work) but I’m wondering whether that should not be used to check that if both attributes are present, their value must be the same.

I’ll try updating the code above at some point.

@JayPanoz
Copy link

JayPanoz commented Jun 15, 2019

Here you go, this one will check if language tag is valid, and whether values of xml:lang and lang match.

I refrained from refactoring too much so that the process be kinda clearer/commented but turned it into functions so that it can be refactored into a JS module more easily – so anyone please feel free to do that if re-using the logic (npm, etc.) and share it.

// UTILS

// Strips leading or trailing spaces from string 
const trimmer = (string) => {
  return string.trim();
}
// Checks if lang is specified
const langIsSpecified = (string) => {
  string = trimmer(string);
  if (string.length > 1) {
    return true;
  } else {
    return false;
  }
}
// Checks if string contains leading or trailing spaces
const hasWhitespace = (string) => {
  return string.length > trimmer(string).length;
}
// Find and checks validity of the lang for the given element
const findLangForEl = (el) => {
  const langValue = el.lang;
  if (langIsSpecified(langValue)) {
    // If lang is specified

    if (!hasWhitespace(langValue)) {
      // If value is valid, return value
      return langValue;
    } else {
      // If lang specified for el have a space, then it’s invalid BCP47 so we throw an error
      console.error(`There is a space in '${langValue}' therefore it isn’t a valid BCP47 language tag for element:`, el);
    }
  }
  return undefined;
}

// FUNCTIONS/METHODS

// Handle XHTML (xml namespace)
const handleXMLLang = () => {
  // Query all elements in the DOM
  const domEls = document.querySelectorAll("*");

  // For each, check if there is an xml:lang
  for (let i = 0; i < domEls.length; i++) {
    const el = domEls[i];

    if (el.hasAttribute("xml:lang") && el.hasAttribute("lang")) {
      // if there is, and there’s also a lang, make sure their values match:
      const xmlValue = el.getAttribute("xml:lang");
      const langValue = el.getAttribute("lang");
      if (xmlValue !== langValue) {
        // If value is different then it’s an error
        console.error(`Langs don’t match for element:`, el);
      }
    } else if (el.hasAttribute("xml:lang") && !el.hasAttribute("lang")) {
      // if there is xml:lang but not lang, then add it 
      // Note: this function must be called first so that code below takes over
      const langValue = el.getAttribute("xml:lang");
      el.setAttribute("lang", langValue);
    }
  }
};

const checkMainLang = () => {
  // Checking if lang specified for html and body 
  const docLang = findLangForEl(document.documentElement);
  const bodyLang = findLangForEl(document.body);

  if (docLang && bodyLang) {
    // If both HTML and BODY langs are specified

    if (docLang !== bodyLang) {
      // We check if they match, and warn if they don’t
      console.warn(
        `HTML and BODY langs don’t match. HTML is '${docLang}' while BODY is '${bodyLang}'. Main lang will be '${bodyLang}'.`
      );
    } else {
      // Else we use html’s
      console.log(`Main lang is '${docLang}'.`);
    }
  } else if (docLang && !bodyLang) {
    // If it’s specified for html
    console.log(`Main lang is '${docLang}'.`);
  } else if (bodyLang && !docLang) {
    // If it’s specified for body
    console.warn(`Main lang is '${bodyLang}' but only set on BODY. Elements such as <title> consequently don’t inherit a specified lang and could inherit the navigator’s default.`);
  } else {
    // Else we warn no lang is set and the navigator’s default will be used
    console.warn(`No lang is set, it will default to the navigator’s: ${navigator.language}.`)
  }
};

const checkOtherLangs = () => {
  // Other languages start here, with an empty array
  let langs = [];
  // We check all elements in body with a lang attribute
  const els = document.body.querySelectorAll(`*[lang]`);

  // For each, we check if we must add the lang to the array
  for (let i = 0; i < els.length; i++) {
    const el = els[i];
    const langToAdd = findLangForEl(el);

    // If there’s a lang and it isn’t in the array yet, we add it
    if (langToAdd && langs.indexOf(langToAdd) === -1) {
      langs.push(langToAdd);
    }
  }
  // Finally we log all the other languages found.
  console.log(`Other languages found: ${langs.toString().replace(/,/g, ", ")}`);
};

handleXMLLang();
checkMainLang();
checkOtherLangs();

@JayPanoz
Copy link

Also, if you want to check the validity of the BCP47 language tag (pattern) → https://github.com/SafetyCulture/bcp47

This has gone so far that I’m wondering whether it shouldn’t be a module (with a GitHub repo). I could even imagine having/finding some use-cases in the future.

@gregoriopellegrino
Copy link
Author

My idea was simply to save a code that I use from time to time to check the accessibility of EPUB and PDF :) , but the work it's becoming interesting...

@JayPanoz
Copy link

I’ve just pushed an invite – started working on completing this a little bit in a private repo – so it’s fair to enable access for you :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment