I tried making a figure environment that is not a float.
Problem
When two figures are near enough that the second one gets pushed to the next page by the TeX page-breaking mechanism, the first of two gets ignored.
List of Figures
Here we see the output of my homemade list of figures, appropriately named, \mylistoffigures:

Missing Image
Here we see the missing figure-not listed above:

Code
My code is written such that no image.jpg is necessary. I adjusted the label-reference system to use my running counter. This is because I needed a unique number abstraction from the printed counter, because I reset the printed \thefigure at every language version, simulated by \setcurrentlanguage.
\documentclass{article}
\usepackage{fontspec}% xelatex
\usepackage{xparse}
\usepackage{environ}
\usepackage{lipsum}
\usepackage{tikz}
\usepackage{hyperref}
\makeatletter
% Define counter
\newcounter{runningfigurecounter}% latex counter, equiv of \global\newcount\runningfigurecounter\runningfigurecounter=0
% Redefine hyperref unique label
\renewcommand*{\theHfigure}{runningfigurecounter.\the\value{runningfigurecounter}} % Sets fifth field in second component of \newlabel to provide unique labels independent of what gets printed i.e. the counter \c@figure etc.
% https://tex.stackexchange.com/questions/273098/mimicking-latexs-table-of-contents-functionality?noredirect=1&lq=1
\long\def\myfigure@addtocontents#1#2{%
\protected@write\@auxout%
{\let\label\@gobble \let\index\@gobble \let\glossary\@gobble}%
{\string\myfigure@writefile{#1}{#2}}}%use our own version of \@writefile
\long\def\myfigure@writefile#1#2{% Redefine our own file handler. This gets called by aux file macros.
\@ifundefined{#1}\relax%
{\@temptokena{#2}%
\immediate\write\csname #1\endcsname{\the\@temptokena}% open file handler
}%
}
\NewDocumentEnvironment{myfigure}{ O{} m O{build:fig:\therunningfigurecounter} }{% \therunningfigurecounter instead \the\runningfigurecounter because it was defined using latex and not tex
% 1: optional graphicx arg
% 2: file
% 3: label
\myfigure@getBODY%
}%
{%
\endmyfigure@getBODY%
\refstepcounter{runningfigurecounter}% global backend figure counter
\@temptokena{\protect\contentsline{figure}{\protect\numberline{\thefigure}\protect{\ignorespaces\myfigurecaption\relax\protect}}{\thepage}{figure.runningfigurecounter.\therunningfigurecounter}}% Contents Line (hyperref format)
\myfigure@addtocontents{lof_\mylanguage}{\the\@temptokena}% Adds \@writefile{lof_en-US}{expanded contents of \@temptokena} to aux
\def\@captype{figure}% see source2e for using caption outside of float
\medbreak% adds \medskip but only if preceding space is less than what \medbreak would insert
\centering% ensure figure and caption is centered within environment
\vbox{% keep stuff on the same page
\IfFileExists{#2}%
{% ensure graphic exists in filesystem
\caption{\myfigurecaption}% handles printed counter logic
\label{#3}% requires redef of \label \ref \autoref \nameref to include build\mylanguage suffix
\includegraphics[width=.8\linewidth,#1]{#2}%
}% True
{%
\caption{\myfigurecaption}% handles printed counter logic
\label{#3}% \requires redef of \label \ref \autoref \nameref to include build:\mylanguage suffix
\tikz \draw [fill=red!15] (0,0) rectangle node [align=center] {Missing Image\\#2} (.8\linewidth, 50mm);
\typeout{Error: Missing figure file #2.}%
}% False
}% end vbox
\medbreak% adds \medskip but only if preceding space is less than what \medbreak would insert
}%
\NewEnviron{myfigure@getBODY}% The goal of collecting \BODY of myfigure is to separate translated text from LaTeX code.
{\global\let\myfigurecaption\BODY}%
\NewDocumentCommand{\mylistoffigures}{}{% Provides list of figures local, works with \@mystarttoc
% to a language
\newpage\section{\listfigurename
\@mkboth{%
\MakeUppercase\listfigurename}{\MakeUppercase\listfigurename}}%
\@mystarttoc{lof_\mylanguage}\newpage% Language-specific lof file is only made if \mylistoffigures called. %\vfill% \vspace*{glue} fill vertical space below line in which is appears %\vfill ends graf immediately ad add vertical space
}
\def\@mystarttoc#1{% imitates latex \@starttoc, except removes tf@ prefix to file handle
\begingroup
\makeatletter
\@input{\jobname.#1}%
\if@filesw% Controls whether writing is enabled. Controls whether writing is enabled. Conditional returns false if \nofiles is issued and no writing to aux has been performed.
\expandafter\newwrite\csname #1\endcsname% make file handle
\immediate\openout \csname #1\endcsname \jobname.#1\relax% open file handle
\fi
\@nobreakfalse
\endgroup}
\makeatother
\NewDocumentCommand\setcurrentlanguage{ m }{\edef\mylanguage{#1}}
\begin{document}
\setcurrentlanguage{en-US}
\mylistoffigures
\newpage
\begin{myfigure}{image.jpg}[fig:label]
Apples.
\end{myfigure}
\lipsum[1-3]
filler
filler
\begin{myfigure}{image.jpg}[fig:label]
Bananas.
\end{myfigure}
\lipsum[1-3]
\newpage
\begin{myfigure}{image.jpg}[fig:label]
Pears.
\end{myfigure}
\end{document}

\myfigure@writefilelines in the aux file in this version and the original) you can't do an immediate write to the aux file as you will get the wrong page number in general. – David Carlisle Oct 31 '17 at 07:45\protected@write\@auxoutis like a data holding tank, which in my version is holding\@temptokena, waiting to be expanded and written by a\shipoutcall. In your version, it is holding a serious of tokens that have been semi-expanded (except protected ones and\thepage, which get expanded by\shipout). Is that the right intepretation? – Jonathan Komar Oct 31 '17 at 09:21\protect) but also prevents expansion of\thepageand then puts it all in a non immediate write to be expanded again in shipout, this time with\thepageexpanding normally. @JonathanKomar – David Carlisle Oct 31 '17 at 09:24