User:Jon Harald Søby/ordbøkenetools

These are the main tools I use to help me create lexemes in Norwegian Bokmål (nb) and Norwegian Nynorsk (nn) using Ordbøkene.no as a source. The main tool, of course, is Wikidata Lexeme Forms, but I have written some scripts to help me pre-fill the forms in Lexeme Forms, which – while pretty specific for Norwegian – may serve as inspiration for those who wish to do something similar for other languages.

User scripts edit

Nikki scripts edit

I use Nikki's excellent scripts LexemeEntitySuggester.js, LexemeAddIPA.js and LexemeInterwikiLinks.js, in addition to the improved KeyShortcuts.js. My settings for these in my common.js are as follows:

Nikki scripts  
// [[User:Nikki/KeyShortcuts.js]]
mw.loader.load( "//www.wikidata.org/w/index.php?title=User:Nikki/KeyShortcuts.js&action=raw&ctype=text/javascript" );
// [[User:Nikki/LexemeEntitySuggester.js]]
mw.loader.load( "//www.wikidata.org/w/index.php?title=User:Nikki/LexemeEntitySuggester.js&action=raw&ctype=text/javascript" );
// [[User:Nikki/LexemeAddIPA.js]]
addipa_p5237 = { "Q25167": "Q6457972" };
addipa_alwaysshown = true;
mw.loader.load("//www.wikidata.org/w/index.php?title=User:Nikki/LexemeAddIPA.js&action=raw&ctype=text/javascript");
// [[User:Nikki/LexemeInterwikiLinks.js]]
mw.loader.load("//www.wikidata.org/w/index.php?title=User:Nikki/LexemeInterwikiLinks.js&action=raw&ctype=text/javascript");

In addition, I made my own "extension" to the Add IPA script which validates and simplifies input for IPA:

Add IPA "extension"  
function JHSipa() {
	var $ipas = $(".addipa-ipa-input input"),
	    $firstIpa = $ipas.first(),
	    allchanged = false;
	setTimeout( function() { $firstIpa.focus(); }, 100 );
	$firstIpa.change( function() {
		if ( !allchanged ) {
			$(".addipa-ipa-input input:not(:first)").val($firstIpa.val().split("¦")[0].replace(/\/$/, "") );
			allchanged = true;
		}
	}).blur( function() {
		$( this ).val( $( this ).val().replace("¦", "") );
	});
	$(".addipa-ipa-input input:not(:first)").focus( function() {
		var that = this;
		var strlen = $( this ).val().length;
		setTimeout( function() {
			that.setSelectionRange( strlen, strlen );
		}, 0);
	}).blur( function() {
		$( this ).val( $( this ).val().replace( /[ˈ²]?(.*)([ˈ²])$/, "$2$1" ) );
		if ( !( $( this ).val().includes(".") ) ) {
			$( this ).val( $( this ).val().replace(/[ˈ²]/, "") );
		}
	});
	$ipas.blur( function() {
		if ( /([:\'agrv]|[eioyø][\u0300\u0302][^ː]|[əɛɪɔʏœ]ː|^(?=.*\.)(?!.*[ˈ²]).*$|^(?!.*\.)(?=.*[ˈ²]).*$)/.test( $( this ).val() ) ) {
			$( this ).css( { "background": "pink", "transition": "all 0.5s" } );
		} else {
			$( this ).css( "background", "#fff" );
		}
		var valsplit = $( this ).val().split( "¦" );
		if ( valsplit.length > 2 ) {
			var ret = [];
			for ( var i = 1; i < valsplit.length; i++ ) {
				ret.push( valsplit[ 0 ] + valsplit[ i ] );
			}
			$( this ).val( ret.join( "|" ) );
		}
	});
	$ipas.last().blur( function() {
		$ipas.each( function() {
			var val = $( this ).val(),
				altreg = /\[(.)(.)\]/;
			if ( altreg.test( val ) ) {
				val = val.replace( altreg, '$1' ) + '|' + val.replace( altreg, '$2' );
			}
			val = val.replace( /(^(?!\/)|(?<!\/)$)/g, "/" ).replaceAll( "|", "/|/" ).replaceAll( "//", "/" ).replace( /^\/$/, "" );
			// exchange ˈ and ² with proper IPA
			let oldvals = val.split( '|' ),
				newvals = [];
			for ( let vall of oldvals ) {
				if ( ( vall.includes( 'ˈ' ) || vall.includes( '²' ) ) && !vall.includes( '̀' ) && !vall.includes( '̂' ) ) {
					const vowel = /([eiouyæøɑɛəɪɔʉʏœ])/,
						  toneme1 = '̀', // U+0300
						  toneme2 = '̂'; // U+0302
					let split = vall.split( /[ˈ²]/ ),
						change = split[ 1 ],
						joiner = '';
					if ( vall.includes( 'ˈ' ) && !vall.includes( toneme1 ) && !vall.includes( toneme2 ) ) {
						change = change.replace( vowel, '$1' + toneme1 );
						joiner = 'ˈ';
					} else if ( vall.includes( '²' ) ) {
						change = change.replace( vowel, '$1' + toneme2 );
						joiner = 'ˈ';
					}
					vall = split[ 0 ] + joiner + change;
				}
				newvals.push( vall );
			}
			val = newvals.join( '|' );
			$( this ).val( val );
			$( this ).change();
		});
	});
}
mw.hook("addipa-dialog-opened").add(JHSipa);

My own scripts edit

copySenses.js lets you copy senses from one lexeme to another. You can either copy all senses, by using just a lexeme ID (like L12345), or copy a single sense by using a sense ID (like L12345-S6). Include it with:

// [[User:Jon Harald Søby/ordbokIframe.js]]
// Remove the two slashes from the next line to make the script not add translation (P5972) statements when copying senses:
// mw.config.set( 'userjs-copysenses-excludetranslations', true );
mw.loader.load( '//www.wikidata.org/w/index.php?title=User:Jon_Harald_Søby/ordbokIframe.js&action=raw&ctype=text/javascript' );

ordbokIframe.js adds an iframe with the contents of Ordbøkene's entry/entries for the current lemma on the right-hand side of lexeme pages. Include it with:

// [[User:Jon Harald Søby/ordbokIframe.js]]
mw.loader.load( '//www.wikidata.org/w/index.php?title=User:Jon_Harald_Søby/ordbokIframe.js&action=raw&ctype=text/javascript' );

bøyningsklasse.js (based on Nikki's LexemeP31Menu.js) adds convenient links for statements I add often, especially paradigm class (P5911)). Include it with:

// [[User:Jon Harald Søby/bøyningsklasse.js]]
mw.loader.load( '//www.wikidata.org/w/index.php?title=User:Jon_Harald_Søby/bøyningsklasse.js&action=raw&ctype=text/javascript' );

hashStatements.js will add statements automatically when they are specified with hashes in the URL, separated with pipes. This can efficiently be used with Lexeme Forms' "target_hash" URL parameter. This script works for all item types, not only lexemes, though my main use for it is for lexemes. If you enable this script and visit https://www.wikidata.org/wiki/Q4115189#P31:Q5|P10042:50077, it will automatically add instance of (P31)human (Q5) and Bokmålsordboka-ID (P10042)50077 to Wikidata Sandbox (Q4115189). Include it with:

// [[User:Jon Harald Søby/hashStatements.js]]
mw.loader.load( '//www.wikidata.org/w/index.php?title=User:Jon_Harald_Søby/hashStatements.js&action=raw&ctype=text/javascript' );

Violentmonkey scripts edit

I use Violentmonkey in Firefox, which is a Greasemonkey fork. These scripts will probably work in Greasemonkey too, but may need some small adjustments, though I haven't tested that.

Add Lexeme Forms links to Ordbøkene.no edit

This script adds pre-filled links for the lemmas directly in Ordbøkene.no. So if you search for a word there, all you have to do is click the lemma to create a lexeme for it (and Lexeme Forms itself will tell you if it already exists or not).

Add Lexeme Forms links to the Bokmål dictionary  
// ==UserScript==
// @name        Ordbøkene + Lexeme-Forms
// @namespace   Violentmonkey Scripts
// @include     https://ordbokene.no/*
// @grant       unsafeWindow
// @version     2.3 (2024-04-15)
// @author      Jon Harald Søby
// @description 22.12.2021, 15:25:31
// @require       https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.js
// ==/UserScript==
// jshint esversion: 8

$( 'html' ).css( 'font-size', '12px' );

const inflectionclasses = {
  'm1': 'Q97097665',
  'm2': 'Q97097666',
  'm3': 'Q97097667',
  'f1': 'Q97097668',
  'f2': 'Q97097669',
  'f3': 'Q97097670',
  'n1': 'Q97097671',
  'n2': 'Q97097672',
  'n3': 'Q97097673',
  'a1': 'Q97097692',
  'a2': 'Q97097693',
  'a3': 'Q97097694',
  'a4': 'Q97097695',
  'a5': 'Q97097697',
  'v1': 'Q97097688',
  'v2': 'Q97097689',
  'v3': 'Q97097690',
  'v4': 'Q97097691'
};

const lexemeforms = {
  'bm': {
    'noun-m': 'https://lexeme-forms.toolforge.org/template/bokmål-noun-masculine/',
    'noun-f': 'https://lexeme-forms.toolforge.org/template/bokmål-noun-feminine/',
    'noun-n': 'https://lexeme-forms.toolforge.org/template/bokmål-noun-neuter/',
    'noun-mn': 'https://lexeme-forms.toolforge.org/template/bokmål-noun-masculine-neuter/',
    'adj': 'https://lexeme-forms.toolforge.org/template/bokmål-adjective/',
    'verb': 'https://lexeme-forms.toolforge.org/template/bokmål-verb/',
    'verb-s': 'https://lexeme-forms.toolforge.org/template/bokmål-verb-passive/',
    'interj': 'https://lexeme-forms.toolforge.org/template/bokmål-interjection/',
    'adv': 'https://lexeme-forms.toolforge.org/template/bokmål-adverb/',
    'DEFAULT': 'https://www.wikidata.org/wiki/Special:NewLexeme?lexeme-language=Q25167'
  },
  'nn': {
    'noun-m': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-masculine/',
    'noun-f': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-feminine/',
    'noun-n': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-neuter/',
    'noun-fm': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-feminine-masculine/',
    'noun-fn': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-feminine-neuter/',
    'noun-mn': 'https://lexeme-forms.toolforge.org/template/nynorsk-noun-masculine-neuter/',
    'adj': 'https://lexeme-forms.toolforge.org/template/nynorsk-adjective/',
    'verb': 'https://lexeme-forms.toolforge.org/template/nynorsk-verb/',
    'verb-s': 'https://lexeme-forms.toolforge.org/template/nynorsk-verb-passive/',
    'interj': 'https://lexeme-forms.toolforge.org/template/nynorsk-interjection/',
    'adv': 'https://lexeme-forms.toolforge.org/template/nynorsk-adverb/',
    'DEFAULT': 'https://www.wikidata.org/wiki/Special:NewLexeme?lexeme-language=Q25164'
  }
};

const nonLexemeformsCategories = {
  'ADP': 'Q4833830',
  'CCONJ': 'Q36484',
  'DET': 'Q576271',
  'EXPR': 'Q187931',
  'NOUN_uninfl': 'Q1084',
  'PFX': 'Q134830',
  'PRON': 'Q36224',
  'SCONJ': 'Q11655558'
}

async function fetchOda( lang, oid ) {
  const oda = await fetch( 'https://oda.uib.no/opal/prod/' + lang + '/article/' + oid + '.json')
    .then( ( response ) => response.json() )
    .then( ( data ) => data );
  return oda;
}

async function processOda( lang, oid ) {
  let oda = await fetchOda( lang, oid ),
      lemmas = {},
      idprop = 'bm' === lang ? 'P10042' : 'P10041',
      copylemmas = {};
  for ( let l of oda.lemmas ) {
    let lemma = l.lemma;
    lemmas[ lemma ] = {
      'id': oda.article_id,
      'statements': [],
      'forms': [],
      'lexemeform': 'DEFAULT',
      'urlsuffix': '?generated_via=[[User:Jon Harald Søby/ordbøkenetools|ordbøkenetools]]'
    };
    lemmas[ lemma ].statements.push( idprop + ':' + oda.article_id );
    let inflectionclass = ( l.hasOwnProperty( 'inflection_class' ) && l.inflection_class ) ? l.inflection_class.split( ',' ) : [];
    inflectionclass.map( x => x.trim() );
    let iclassstatement = [];
    for ( let iclass of inflectionclass ) {
      if ( inflectionclasses.hasOwnProperty( iclass ) ) {
        iclassstatement.push( 'P5911:' + inflectionclasses[ iclass ] );
      } else if ( inflectionclass.includes( 'verb' ) ) {
        iclassstatement = [ 'P31:Q70235' ];
        break;
      } else {
        iclassstatement = null;
        break;
      }
    }
    if ( iclassstatement ) {
      lemmas[ lemma ].statements = lemmas[ lemma ].statements.concat( iclassstatement );
    }
    let inflection_group = new Set(),
        paradigmtags = new Set(),
        maintag;
    for ( let paradigm of l.paradigm_info ) {
      if ( paradigm.to ) continue;
      for ( let tag of paradigm.tags ) {
        paradigmtags.add( tag );
        if ( /^[A-Z]+$/.test( tag ) ) {
          maintag = tag;
        }
      }
      inflection_group.add( paradigm.inflection_group );
      if ( paradigm.inflection_group === 'NOUN_uninfl' ) {
        maintag = 'NOUN_uninfl';
      }
    }
    paradigmtags.delete( '<Ord>' );
    inflection_group = [...inflection_group];
    paradigmtags = [...paradigmtags];
    let cont = true;
    if ( paradigmtags.length > 3 ) {
      lemmas[ lemma ].forms = [ lemma ];
      cont = false;
    }
    if ( cont && paradigmtags.length === 3 ) {
      if (
        lang === 'bm' &&
        maintag === 'NOUN' &&
        paradigmtags.includes( 'Masc' ) &&
        paradigmtags.includes( 'Fem' )
      ) {
        lemmas[ lemma ].lexemeform = 'noun-f';
        let mascforms = [ ...new Array( 4 ) ].map( () => new Set() );
        let femforms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          if ( paradigm.tags.includes( 'Masc' ) ) {
            for ( let form in paradigm.inflection ) {
              mascforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          } else if ( paradigm.tags.includes( 'Fem' ) ) {
            for ( let form in paradigm.inflection ) {
              femforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
        }
        mascforms = mascforms.map( x => [ ...x ].join( '/' ) );
        femforms = femforms.map( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = [ mascforms[ 0 ], femforms[ 1 ], mascforms[ 1 ], mascforms[ 2 ], mascforms[ 3 ] ];
      } else if (
        lang === 'bm' &&
        maintag === 'NOUN' &&
        paradigmtags.includes( 'Masc' ) &&
        paradigmtags.includes( 'Neuter' )
      ) {
        lemmas[ lemma ].lexemeform = 'noun-mn';
        let mascforms = [ ...new Array( 4 ) ].map( () => new Set() );
        let neutforms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          if ( paradigm.tags.includes( 'Masc' ) ) {
            for ( let form in paradigm.inflection ) {
              mascforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          } else if ( paradigm.tags.includes( 'Neuter' ) ) {
            for ( let form in paradigm.inflection ) {
              neutforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
        }
        mascforms = mascforms.map( x => [ ...x ].join( '/' ) );
        neutforms = neutforms.map ( x => [ ...x ].join( '/' ) );
        let forms = [];
        forms.push( mascforms[ 0 ], mascforms[ 1 ] );
        forms.push( neutforms[ 1 ] );
        forms.push( mascforms[ 2 ] );
        if ( neutforms[ 2 ] === mascforms[ 2 ] ) {
          forms.push( '' );
        } else {
          forms.push( neutforms[ 2 ] );
        }
        forms.push( mascforms[ 3 ] );
        let defneutpl = neutforms[ 3 ].split( '/' );
        for ( let f of defneutpl ) {
          if ( f !== mascforms[ 3 ] ) {
            forms.push( f );
          }
        }
        lemmas[ lemma ].forms = forms;
      } else if (
        lang === 'nn' &&
        maintag === 'NOUN' &&
        paradigmtags.includes( 'Fem' ) &&
        paradigmtags.includes( 'Masc' )
      ) {
        lemmas[ lemma ].lexemeform = 'noun-fm';
        let femforms = [ ...new Array( 4 ) ].map( () => new Set() );
        let mascforms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          if ( paradigm.tags.includes( 'Masc' ) ) {
            for ( let form in paradigm.inflection ) {
              mascforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          } else if ( paradigm.tags.includes( 'Fem' ) ) {
            for ( let form in paradigm.inflection ) {
              femforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
        }
        femforms = femforms.map( x => [ ...x ].join( '/' ) );
        mascforms = mascforms.map ( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = [ femforms[ 0 ], femforms[ 1 ], mascforms[ 1 ], femforms[ 2 ], mascforms[ 2 ], femforms[ 3 ], mascforms[ 3 ] ];
      } else if (
        lang === 'nn' &&
        maintag === 'NOUN' &&
        paradigmtags.includes( 'Fem' ) &&
        paradigmtags.includes( 'Neuter' )
      ) {
        lemmas[ lemma ].lexemeform = 'noun-fn';
        let femforms = [ ...new Array( 4 ) ].map( () => new Set() );
        let neutforms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          if ( paradigm.tags.includes( 'Fem' ) ) {
            for ( let form in paradigm.inflection ) {
              femforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          } else if ( paradigm.tags.includes( 'Neuter' ) ) {
            for ( let form in paradigm.inflection ) {
              neutforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
        }
        femforms = femforms.map( x => [ ...x ].join( '/' ) );
        neutforms = neutforms.map ( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = [ femforms[ 0 ], femforms[ 1 ], neutforms[ 1 ], femforms[ 2 ], neutforms[ 2 ], femforms[ 3 ], neutforms[ 3 ] ];
      } else if (
        lang === 'nn' &&
        maintag === 'NOUN' &&
        paradigmtags.includes( 'Masc' ) &&
        paradigmtags.includes( 'Neuter' )
      ) {
        lemmas[ lemma ].lexemeform = 'noun-mn';
        let mascforms = [ ...new Array( 4 ) ].map( () => new Set() );
        let neutforms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          if ( paradigm.tags.includes( 'Masc' ) ) {
            for ( let form in paradigm.inflection ) {
              mascforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          } else if ( paradigm.tags.includes( 'Neuter' ) ) {
            for ( let form in paradigm.inflection ) {
              neutforms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
        }
        mascforms = mascforms.map( x => [ ...x ].join( '/' ) );
        neutforms = neutforms.map ( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = [ mascforms[ 0 ], mascforms[ 1 ], neutforms[ 1 ], mascforms[ 2 ], neutforms[ 2 ], mascforms[ 3 ], neutforms[ 3 ] ];
      }
    } else if ( cont && paradigmtags.length === 2 ) {
      if ( maintag === 'NOUN' ) {
        let lexemeform;
        if ( paradigmtags.includes( 'Masc' ) ) {
          lexemeform = 'noun-m';
        } else if ( paradigmtags.includes( 'Neuter' ) ) {
          lexemeform = 'noun-n';
        } else if ( paradigmtags.includes( 'Fem' ) ) {
          lexemeform = 'noun-f';
        }
        lemmas[ lemma ].lexemeform = lexemeform;
        let forms = [ ...new Array( 4 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          for ( let form in paradigm.inflection ) {
            forms[ form ].add( paradigm.inflection[ form ].word_form );
          }
        }
        forms = forms.map( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = forms;
      } else {
        lemmas[ lemma ].urlsuffix = '&lemma=' + lemma + '&lexicalcategory=' + nonLexemeformsCategories[ maintag ];
      }
    } else if ( cont && maintag === 'VERB' ) {
      if ( inflection_group[ 0 ] === 'VERB_sPass' ) {
        lemmas[ lemma ].lexemeform = 'verb-s';
        let forms = [ ...new Array( 5 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          for ( let form in paradigm.inflection ) {
            forms[ form ].add( paradigm.inflection[ form ].word_form );
          }
        }
        forms = forms.map( x => [ ...x ].join( '/' ) );
        lemmas[ lemma ].forms = forms;
      } else {
        lemmas[ lemma ].lexemeform = 'verb';
        let forms = [ ...new Array( 16 ) ].map( () => new Set() );
        for ( let paradigm of l.paradigm_info ) {
          if ( paradigm.to ) continue;
          for ( let form in paradigm.inflection ) {
            forms[ form ].add( paradigm.inflection[ form ].word_form );
          }
        }
        let imperative = [ ...new Set( [ ...forms[ 13 ], ...forms[ 14 ], ...forms[ 15 ] ] ) ].filter( x => !!x ).join( '/' );
        forms = forms.map( x => [ ...x ].join( '/' ) );
        if ( lang === 'bm' ) {
          lemmas[ lemma ].forms = [
            forms[ 0 ],
            forms[ 2 ],
            forms[ 5 ],
            forms[ 6 ],
            imperative,
            forms[ 8 ],
            forms[ 7 ],
            forms[ 10 ],
            forms[ 11 ],
            forms[ 12 ],
            forms[ 3 ],
            forms[ 4 ]
          ];
        } else {
          lemmas[ lemma ].forms = [
            forms[ 1 ],
            forms[ 0 ],
            forms[ 2 ],
            forms[ 5 ],
            forms[ 6 ],
            imperative,
            forms[ 8 ],
            forms[ 7 ],
            forms[ 10 ],
            forms[ 11 ],
            forms[ 12 ],
            forms[ 3 ],
            forms[ 4 ]
          ];
          if ( !lemmas[ lemma ].forms[ 0 ] && lemmas[ lemma ].forms[ 1 ] ) {
            lemmas[ lemma ].forms[ 0 ] = lemmas[ lemma ].forms[ 1 ];
            lemmas[ lemma ].forms[ 1 ] = '';
          } else {
            copylemmas[ lemma ] = lemmas[ lemma ].forms[ 0 ];
          }
        }
      }
    } else if ( cont && paradigmtags.length === 1 ) {
      let forms;
      switch ( maintag ) {
        case 'ADJ':
          forms = [ ...new Array( 7 ) ].map( () => new Set() );
          for ( let paradigm of l.paradigm_info ) {
            if ( paradigm.to ) continue;
            for ( let form in paradigm.inflection ) {
              forms[ form ].add( paradigm.inflection[ form ].word_form );
            }
          }
          lemmas[ lemma ].lexemeform = 'adj';
          forms = forms.map( x => [ ...x ].join( '/' ) );
          lemmas[ lemma ].forms = [
            forms[ 0 ],
            forms[ 3 ],
            forms[ 1 ],
            forms[ 2 ],
            forms[ 4 ],
            forms[ 5 ],
            forms[ 6 ]
          ];
          break;
        case 'ADV':
          lemmas[ lemma ].lexemeform = 'adv';
          lemmas[ lemma ].forms = [ lemma ];
          break;
        case 'INTJ':
          lemmas[ lemma ].lexemeform = 'interj';
          lemmas[ lemma ].forms = [ lemma ];
          break;
        default:
          lemmas[ lemma ].urlsuffix = '&lemma=' + lemma + '&lexicalcategory=' + nonLexemeformsCategories[ maintag ];
      }
    }

    if (
      [ 'NOUN', 'VERB' ].includes( maintag ) &&
      lemmas[ lemma ].forms.includes( '' )
    ) {
      lemmas[ lemma ].urlsuffix = 'advanced/' + lemmas[ lemma ].urlsuffix;
    }
  }
  for ( let lemma in copylemmas ) {
    lemmas[ copylemmas[ lemma ] ] = lemmas[ lemma ];
  }
  return lemmas;
}

async function fetchQuery( lang, oids ) {
  let prop = lang === 'bm' ? 'P10042' : 'P10041';
  oids = '"' + oids.join( '" "' ) + '"';
  let query = `SELECT ?oid ?l ?lemma WHERE {
    VALUES ?oid { ${oids} } .
    ?l wdt:${prop} ?oid .
    ?l wikibase:lemma ?lemma
  }`;
  const res = await fetch( 'https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent( query ) )
    .then( ( response ) => response.json() );
  if ( res.results.bindings.length === 0 ) {
    return false;
  } else {
    let bindings = {};
    for ( let binding of res.results.bindings ) {
      if ( !bindings.hasOwnProperty( binding.oid.value ) ) {
        bindings[ binding.oid.value ] = {};
      }
      bindings[ binding.oid.value ][ binding.lemma.value ] = binding.l.value;
    }
    return bindings;
  }
}


/*
async function dummy( lang, oid ) {
  let process = await processOda( lang, oid );
  let query = await fetchQuery( lang, oid );
  console.log( process );
}

dummy( 'nn', 76670 );
*/

function alphabetnext( qstring ) {
  let newstring = '';
  const alphabet = 'abcdefghijklmnopqrstuvwxyzæøå';
  if ( !/\*$/.test( qstring ) ) return false;
  let lastletter = qstring[ qstring.length - 2 ];
  if ( lastletter === 'å' ) {
    newstring = qstring.substring( 0, qstring.length - 2 ) + '*';
    newstring = alphabetnext( newstring );
  } else {
    let lastpos = alphabet.indexOf( lastletter );
    newstring = qstring.substring( 0, qstring.length - 2 ) + alphabet[ lastpos + 1 ] + '*';
  }
  return newstring;
}


setTimeout( async function() {
  $( document ).ready( async function() {
    let bm_oids = [],
        nn_oids = [];
    $( '.article a.whitespace-nowrap' ).each( async function() {
      let id = $( this ).attr( 'href' ).split( '/' ),
          lang = id[ 2 ],
          oid = id[ 3 ];
      if ( lang === 'bm' ) {
        bm_oids.push( oid );
      } else {
        nn_oids.push( oid );
      }
    });
    let bm_query = await fetchQuery( 'bm', bm_oids ),
        nn_query = await fetchQuery( 'nn', nn_oids );
    $( '.article' ).each( async function() {
      let id = $( this ).find( 'a.whitespace-nowrap[href^="/nno/"]' ).attr( 'href' ).split( '/' ),
          lang = id[ 2 ],
          oid = id[ 3 ],
          $h3 = $( this ).find( 'h3' );
      let oda = await processOda( lang, oid ),
          query = lang === 'bm' ? bm_query : nn_query,
          $a = $( '<a>' );
      $h3.each( function() {
        let h3text = $( this ).text();
        let theselemmas = h3text.split( ', ' ),
            newhtml;
        theselemmas = theselemmas.map( x => '<span><span class="lexlemma">' + x + '</span></span>' );
        newhtml = theselemmas.join( '<span><span class="title-comma">, </span></span>' );
        $( this ).html( newhtml );
      } );
      let $lemmas = $( this ).find( 'span.lexlemma' );
      console.log( $lemmas );
      $lemmas.each( async function() {
        let lemma = $( this ).text().trim();
        let href = lexemeforms[ lang ][ oda[ lemma ].lexemeform ];
        href = href + oda[ lemma ].urlsuffix;
        if ( oda[ lemma ].lexemeform !== 'DEFAULT' ) {
          href = href + [ '' ].concat( oda[ lemma ].forms ).join( '&form_representation=' );
          href = href + '&target_hash=' + oda[ lemma ].statements.join( '|' );
        }
        if ( query && query.hasOwnProperty( oid ) && query[ oid ].hasOwnProperty( lemma ) ) {
          let lid = query[ oid ][ lemma ].split( '/' )[ 4 ];
          $( this ).wrapInner( $a.attr( 'href', query[ oid ][ lemma ] ).attr( 'style', 'color: green !important;' ) );
          $( this ).after( $( '<sup><a href="' + href.replace( /(advanced\/)?\?/, 'edit/' + lid + '?' ) + '">rediger</a></sup>') );
        } else {
          $( this ).wrapInner( $a.attr( 'href', href ) ).attr( 'style', 'color: orange !important;' );
        }
      } );
      let $copyid = $( '<p>' ).css( { color: 'red', cursor: 'copy', border: 'none', 'text-align': 'right' } ).text( 'OID: ' + oid ).on( 'click', async function() {
        await navigator.clipboard.writeText( oid );
        $( this ).css( 'color', 'green' );
      } );
        $( this ).prepend( $copyid );
      } );
    } );
}, 1000);

Add Ordbøkene.no to Lexeme Forms edit

This is the Lexeme Forms version of the ordbokIframe.js script – it adds an iframe with the results from Ordbøkene.no for the current lexeme. Handy to have access to all the right forms in case the word is irregular.

Add the Bokmål dictionary to Lexeme Forms  
// ==UserScript==
// @name        Ordbøkene i Lexeme Forms
// @namespace   Violentmonkey Scripts
// @match       https://lexeme-forms.toolforge.org/template/*
// @grant       unsafeWindow
// @version     2.0
// @author      Jon Harald Søby
// @description 17.5.2021, 14:29:32
// @require       https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.js
// ==/UserScript==

$(document).ready(function() {
  var lang = $( 'main' ).attr( 'lang' ),
      firstform = $( 'input[name=form_representation]:first' ).val();
  if ( lang === 'nb' || lang === 'nn' ) {
    var src = 'https://ordbokene.no/bm/search?q=';
    if ( lang === 'nn' ) src = 'https://ordbokene.no/nn/search?q=';
    $( "main" ).css( "max-width", "1400px" );
    $( "main form" ).wrap( '<div class="cols" />' );
    $( ".cols" ).css( { 'display': 'grid', 'grid-template-columns': '50% calc(50% - 2em)', 'grid-gap': '2em' } );
    $( ".cols" ).append(
      $( '<iframe />' )
        .css( { 'width': '100%', 'height': '70vh', 'border': 0, 'box-shadow': '0 3px 5px #ccc', 'position': 'sticky', 'top': 0 } )
        .attr( 'src', src + firstform ) );
    setTimeout( function() {
      $( 'input[name=form_representation]:first' ).focus();
    }, 1500 );
  }
});