get_posts suppresses filters and WP_Query does not

get_posts() is a thin wrapper over WP_Query, so they are usually described as interchangeable. They are not: get_posts() defaults suppress_filters to true, which disables every posts_* filter for that query.

// plugin filters on posts_where / posts_join do NOT run
get_posts( array( 'post_type' => 'product' ) );

// they do run
new WP_Query( array( 'post_type' => 'product' ) );

// or opt back in explicitly
get_posts( array( 'post_type' => 'product', 'suppress_filters' => false ) );

This is why a multilingual plugin returns the wrong language from one call and the right one from another, in the same file. It also defaults ignore_sticky_posts to true and posts_per_page to 5 rather than the site setting. Neither is wrong; both are surprising if you assumed the two APIs were the same thing.