Hidden sentences in the license plates

Perl 5, 89 bytes

88 bytes code + 1 for -p.

Requires the numberplate to be in the format ## A A A A and the lists need to be formatted like ("A","B",...). If this is pushing the limits of acceptable, please let me know.

@$_=eval<>for n,v,a;s!\pL!(grep/^$&/,@{(rand>.5?(a,n,v,n):(n,v,a,n))[$-++]})[rand 5]!egi

Try it online!


JavaScript (ES6), 124 120 118 bytes

Takes input in currying syntax (s)(a) where s is a string in "NNCCCC" format and a is the array of arrays of words [nouns, verbs, adj].

s=>a=>s.replace(/\D/g,c=>' '+(g=a=>a.sort(R)[0][0]==c?a.shift(i/=4):g(a))(a[i&3]),i=(R=_=>Math.random()-.5)()<0?18:36)

Test cases

let f =

s=>a=>s.replace(/\D/g,c=>' '+(g=a=>a.sort(R)[0][0]==c?a.shift(i/=4):g(a))(a[i&3]),i=(R=_=>Math.random()-.5)()<0?18:36)

const NOUNS = ["Aanbeelden","Aardbeien","Apen","Appels","Auto's","Bananen","Bessen","Bloemen","Bomen","Bijen","Cadeaus","Cavia's","Cheerleaders","Circusdieren","Citroenen","Dinosaurussen","Dieren","Documenten","Doctoren","Dolfijnen","E-mails","Edelstenen","Eekhoorns","Enqûetes","Ezels","Fabrieken","Fietsen","Flessen","Fontijnen","Foto's","G-spots","Giraffen","Glazen","Gymschoenen","Gummen","Haaien","Homo's","Honden","Hoofden","Horloges","Idioten","Idolen","IJsklontjes","Illusies","Imperia","Jassen","Jokers","Jongleurs","Jurken","Juwelen","Kabels","Katten","Kauwgomballen","Knoppen","Konijnen","Leesboeken","Leeuwen","Legercommandanten","Lesbies","Lummels","Maagden","Mannen","Mensen","Mieren","Muren","Neuzen","Nieuwkomers","Nonnen","Noten","Nummerborden","Oceanen","Ogen","Olifanten","Oren","Orkanen","PC's","Pizza's","Politieagenten","Postbezorgers","Potloden","Quads","Quaggamosselen","Querulanten","Quiëtisten","Quizzen","Radio's","Ramen","Regiseuren","Rekenmachines","Relschoppers","Schepen","Slakken","Slangen","Stegosaurussen","Stoelen","T-rexen","Tafels","Theekopjes","Tijgers","Tovenaars","Uien","Uilen","Universa","Universiteiten","USB-sticks","Vliegtuigen","Vrachtwagens","Vrienden","Vrouwen","Vulkanen","Walvissen","Wespen","Wormen","Worstelaars","Wortels","X-assen","X-chromosomen","Xenonlampen","Xylofonen","Xylofonisten","Y-assen","Y-chromosomen","Yoghurtdranken","Yogaleraren","Yogaleraressen","Zeeën","Zeehonden","Zeepbellen","Zwanen","ZZP'ers"];
const VERBS = ["Accepteren","Activeren","Amputeren","Amuseren","Assisteren","Bakken","Bedreigen","Bellen","Bevruchten","Bijten","Castreren","Categoriseren","Centrifugeren","Claimen","Coachen","Dagvaardigen","Daten","Degraderen","Dienen","Dwarsbomen","Electroniseren","Eren","Erven","Eten","Exploderen","Factureren","Fokken","Föhnen","Fotographeren","Frituren","Gooien","Graderen","Grijpen","Groeperen","Groeten","Hacken","Hallucineren","Haten","Helpen","Huren","Importeren","Inspireren","Intelligente","Irriteren","Isoleren","Jagen op","Jatten","Jennen","Jongleren met","Justeren","Klonen","Koken","Kopen","Kraken","Kussen","Labelen","Leasen","Lonken","Loven","Lusten","Maken","Martelen","Merken","Meten","Mollen","Negeren","Neuken","Neutraliseren","Normaliseren","Notificeren","Observeren","Oliën","Ontbloten","Ontsmetten","Ordenen","Penetreren","Pesten","Plagen","Porren","Produceren","Quadrilleren met","Quadrupleren","Queruleren over","Quoteren","Quotiseren","Raadplegen","Redden","Reinigen","Ruilen","Roken","Schieten","Schoppen","Schudden","Slaan","Stoppen","Tackelen","Telen","Tellen","Temperen","Torpederen","Unificeren","Unlocken","Upgraden","Urineren","Utiliseren","Verbinden","Verjagen","Verkopen","Vermoorden","Verwijderen","Wassen","Wekken","Wijzigen","Wreken","Wurgen","Xeroxen","Xoipen","Xeroxen","Xoipen","X-en","Yammeren","Yellen","Yielding","YouTube-streamen","Youtuben","Zegenen","Zieken","Zien","Zoeken","Zoenen"];
const ADJECTIVES = ["Abstracte","Achterlijke","Afrikaanse","Amerikaanse","Angstaanjagende","Baby","Blauwe","Blote","Boze","Broze","Charmante","Chinese","Chocolade","Coole","Corrupte","Dagdromende","Dansende","Dikke","Dode","Dunne","Echte","Effectieve","Elektrische","Enorme","Europese","Felle","Fictieve","Fietsende","Fluisterende","Fluitende","Gele","Glow-in-the-dark","Grijze","Groene","Grote","Harige","Harde","Homosexuele","Horde","Hyperactieve","Idiote","IJzere","IJzige","Instelbare","Ivoren","Jaloerse","Jammerende","Japanse","Jokkende","Jonge","Kale","Kapotte","Kleine","Knappe","Koude","Lelijke","Lege","Levende","Lopende","Luie","Maffe","Massieve","Middeleeuwse","Milde","Mythische","Naakte","Nachtelijke","Nare","Nucleaire","Normale","Ongelovige","Oranje","Oude","Overheerdende","Oxiderende","Paarse","Platte","Pratende","Piraten","Puzzelende","Quiteense","Quad-rijdende","Quasi","Queue-stotende","Quiche-etende","Rennende","Rode","Romantische","Ronde","Roze","Saaie","Schattige","Scherpe","Springende","Stoffige","Tongzoenende","Transformerende","Transparente","Treiterende","Triomferende","Ultieme","Unieke","Uniseks","Uiteenlopende","Uitmuntende","Vage","Verdrietige","Vierkante","Vliegende","Vrolijke","Warme","Waterige","Wijze","Witte","Wraakzuchtige","Xenofobische","Xhosa-sprekende","XTC-verslaafde","XXX","Xylofoonspelende","Yahoo-zoekende","Yammerende","Yellende","YouTube-streamende","Youtubende","Zachte","Zingende","Zwarte","Zwemmende","Zwevende"];

console.log(f("71NNBT")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("80XWIK")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("13UUEF")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("12AAAA")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("99ZXYW")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("73THQQ")([NOUNS, VERBS, ADJECTIVES]))
console.log(f("04DJIO")([NOUNS, VERBS, ADJECTIVES]))

How?

The sequences of lists to pick the words from are stored as bitmasks, in reverse order:

Noun, Verb, Adjective, Noun --> 0, 1, 2, 0 --> 00 01 10 00 --[reverse]--> 00100100 --> 36
Adjective, Noun, Verb, Noun --> 2, 0, 1, 0 --> 10 00 01 00 --[reverse]--> 00010010 --> 18

That's why i is randomly initialized to either 18 or 36.

s => a =>                           // given 's' and 'a'
  s.replace(                        // replace in 's'
    /\D/g, c =>                     // each non-digit character 'c' with:
    ' ' + (                         // a space followed by
      g = a =>                      // a word picked from the list 'a'
        a.sort(R)                   //   shuffle the list
        [0][0] == c ?               //   if the 1st char. of the 1st word is matching 'c':
          a.shift(i /= 4)           //     return this word (removing it from the list)
                                    //     and discard the 2 least significant bits of 'i'
        :                           //   else:
          g(a)                      //     try again with a recursive call
    )(a[i & 3]),                    // initial call to g() with the next list
    i = (                           // initialize 'i', using:
      R = _ => Math.random() - 0.5  //   R(): returns a random float in [-0,5, 0.5)
    )() < 0 ? 18 : 36               // to either 18 or 36 (see above)
  )                                 // end of replace()

Jelly,  37  35 bytes

ṙ2X¤;⁸ṪW¤i’¥Ðḟ"⁹ṫ3¤ŒpŒQẠ$ÐfXK⁹ḣ2¤,K

A dyadic link taking a list of lists (of lists of characters): [[verbs],[adjectives],[nouns]] on the left and the numberplate (format = NNCCCC) on the right and returning a list of characters.

Try it online!

How?

ṙ2X¤;⁸ṪW¤i’¥Ðḟ"⁹ṫ3¤ŒpŒQẠ$ÐfXK⁹ḣ2¤,K - Link: list of lists of lists, words; list, plate
   ¤                                - nilad followed by link(s) as a nilad:
 2                                  -   literal two
  X                                 -   random integer in [1,z] -------- gets 1 or 2
ṙ                                   - rotate left by - words [v,a,n] -> [a,n,v] or [n,v,a]
        ¤                           - nilad followed by link(s) as a nilad:
     ⁸                              -   chain's left argument, words
      Ṫ                             -   tail - get the nouns
       W                            -   wrap in a list
    ;                               - concatenate - add another noun list to the end
                                    - (call this allWordLists) - either [a,n,v,n] or [n,v,a,n])
                  ¤                 - nilad followed by link(s) as a nilad:
               ⁹                    -   chain's right argument, plate
                 3                  -   literal three
                ṫ                   -   tail from index - get the four letters
                                    - (call this letters)
              "                     - zip with the dyadic operation:
            Ðḟ                      -   filter discard if:
           ¥                        -     last two links as a dyad:
         i                          -       first index of (a letter in a word in a wordList in allWordLists)
          ’                         -       decrement (if not found i yields 0, so this result is only 0 when the first index is the head of the list)
                                    - ...this gives us a list of lists of words beginning with the appropriate letters ordered either as [a,v,n,n] or [n,v,a,n]
                   Œp               - Cartesian product of the filtered lists
                         Ðf         - filter keep if:
                        $           -   last two links as a monad:
                     ŒQ             -     distinct sieve (1s at 1st occurrences, 0s elsewhere)
                       Ạ            -     all truthy - i.e. all distinct?
                           X        - random choice from the resulting list - get one four word choice
                            K       - join with spaces
                                ¤   - nilad followed by link(s) as a nilad:
                             ⁹      -   chain's right argument, plate
                               2    -   literal two
                              ḣ     -   head to index - get the two digits
                                 ,  - pair
                                  K - join with a space