Is there a way to have checkbox field labels translatable?
Short of hardcoding all these checkbox values and applying the translate tag on them


That's actually the only way to translate field names, checkbox labels/values and whatnot: taking advantage of Craft's static text translation feature by hard coding the strings you want to translate in a translations file (e.g. /translations/en.php). 

An example: If one of your whiskey field's option labels is "Irish", but you want it to be "Welsh" for a certain locale, you can add the following to the returned array in that locale's translation file:

'Irish' =&gt; 'Welsh',


Of course, it's also possible to translate the field name itself, i.e.

'Whiskey' =&gt; 'Whisky',


There's a big problem though – Craft's static text translation has no idea about context, meaning that it's not possible to tell Craft that the above translations belong to the whiskey field only. In other words, both Whiskey and Irish will be translated across the board. Sometimes that's what you'd want, but often it's not – a translated value may work in one place, but not in another.

One way to solve this, is to use namespacing for all the static strings you want to translate. The naming convention is entirely up to you, but here's how I do it for fields – the following two values are used for the field's name and instructions, respectively:

field.whiskey.name
field.whiskey.instructions


With this namespacing pattern, the first part is the type of content this string belongs to (a field), the second part is the field's handle (which is obviously never translated) and the last part is what the string actually is (the name and the instructions, respectively). This works for other types of content as well, consider section.news.name, entryType.link.name, categoryGroup.editors.name etc.

For "nested" properties, such as a checkbox option's label, I usually write them out like this:

field.whiskey.option.irish.label


For Matrix blocks, the namespacing scheme admittedly is a little bit ridiculous:

field.someMatrixField.block.bio.name
field.someMatrixField.block.bio.text.name
field.someMatrixField.block.bio.text.instructions


The big downside to namespacing field names, option labels, section names etc. is that you have to add a translation for all locales (even your "default" locale), in order to not have the namespaced, "raw" value displayed to your users. In other words, it's definitely a bit more work than just translating plain ol' strings, but the big win is having granular control over all your translated fields, and being able to translate the same value differently for different use cases. It's the only way to go, in my opinion – at least when you have a fair number of strings to translate.

Anyway, here's how your translations file (e.g. /translations/en.php) could look with namespacing for the whiskey field:

return [
    'field.whiskey.name' =&gt; 'Whiskey',
    'field.whiskey.option.irish.label' =&gt; 'Welsh',
];


As for the frontend, your static string translations will work there, too – just add the |t filter to the labels:

{% for option in entry.whiskey.options %}
    &lt;li&gt;{{ option.label|t }}: {{ option }}&lt;/li&gt;
{% endfor %}