Lazy loading and memoization for services

Improves performance by only loading a service or querying its data when
needed, and memoizing the result into the router instance.
Basic playback implemented in router

This still needs some additional validation and normalization work.
Custom normalization and validation for services

With these additions, services can now specify how user input should be
normalized (e.g. for parsing voices with more leniency) and how user
input should be validated (e.g. by allowing ranged values to work even
if the value the user specified isn't in one of our nice dropdown
lists).
Add text phrase normalization to router

This is done by passing in another lambda, and it can clean up HTML,
sound tags, and excess whitespace.
Remove memoization for service lists

This is so quick to be generated that memoization is not necessary or
worthwhile.
Wire on-the-fly playback to new router

Connects the existing on-the-fly code to go through the new router's
play() method, also fixing the options code to correctly remove keys
that are not part of the options set.
Prevent repeated lookups/index-sets in router

We can do this by noting the last value something was when it was
accessed, allowing us to only update the 'index' property when the value
changes.
Reimplement configuration screen in pure Python

With this change, we get a number of things:

- form is now responsive and sizes to fit its content
- tabs have icons
- users can specify the throttling behavior
- settings that only affect certain services explicitly list which ones