I may have found a fix - turns out it will display both post contents if the posts have the same URL - naturally, being part of a while loop. Just needed to filter out the correct language in single.php after the_post()
(rather crudely, so feel free to point out a better solution):
global $polylang;
if($polylang->get_post_language(get_the_ID())->slug !== pll_current_language()) continue;
There's still an issue with hreflang attributes pointing to the current language domain only, so I'm adding this to frontend-links.php:129 (again, rather crudely, so please feel free to provide an alternative):
if ($url = $this->get_translation_url($language)) {
$url = str_replace(pll_home_url(), pll_home_url($language->slug), $url);
$urls[$language->slug] = $url;
}